Claude Code Hooks in Practice: PreToolUse Patterns for Enforcing Engineering Policy
Production-grade Claude Code hook recipes for blocking secrets, enforcing branch naming, and gating destructive Bash calls. Real settings.json configs.
Table of Contents
- Why Claude Code Hooks Matter for Engineering Teams
- Understanding PreToolUse: The Foundation
- Blocking Secrets in Commits
- Enforcing Branch Naming Conventions
- Gating Destructive Bash Operations
- Real Production Configurations
- Integration with CI/CD Pipelines
- Common Pitfalls and Solutions
- Scaling Hooks Across Teams
- Next Steps and Implementation
Why Claude Code Hooks Matter for Engineering Teams {#why-claude-code-hooks-matter}
Engineering teams at mid-market and enterprise companies face a persistent problem: how do you maintain code quality, security, and compliance standards when AI is writing or modifying code? The answer isn’t to disable AI—it’s to enforce policy at the point of execution.
Claude Code hooks, specifically the PreToolUse pattern, give you deterministic control over what Claude can and cannot do before it executes. This is fundamentally different from post-execution reviews or linting. You’re preventing the problem before it happens.
At PADISO, we’ve worked with 50+ clients shipping agentic AI systems, and the teams that win are those that treat code generation as a policy problem, not a trust problem. When you’re modernising with agentic AI and workflow automation, you need guardrails that actually work.
PreToolUse hooks let you:
- Block credential exposure: Prevent Claude from committing AWS keys, API tokens, or database passwords
- Enforce naming standards: Reject branch names that don’t match your convention (e.g.,
feature/JIRA-123-description) - Gate destructive commands: Stop
rm -rf, database drops, or production deployments without explicit approval - Validate before execution: Check file permissions, SQL syntax, or infrastructure changes before they run
- Maintain audit trails: Log every blocked action for compliance (SOC 2 / ISO 27001 readiness)
This guide covers production-grade recipes you can ship today.
Understanding PreToolUse: The Foundation {#understanding-pretooluse}
Claude Code hooks operate on a lifecycle model. The PreToolUse event fires before Claude executes any tool—a bash command, a file write, a git operation. This is your enforcement point.
When you configure a PreToolUse hook, you’re defining a function that receives:
- Tool name:
bash_execute,file_write,git_commit, etc. - Tool input: The exact command or operation Claude wants to run
- Context: The current project state, environment variables, and previous actions
Your hook function returns one of three decisions:
- Allow (
return nullorreturn { allowed: true }): Claude proceeds - Block (
return { allowed: false, reason: "..." }): Claude sees the rejection and can retry or explain - Transform (
return { allowed: true, input: modifiedInput }): Modify the command before execution
Understanding this flow is critical. You’re not trying to outsmart Claude; you’re setting boundaries that Claude respects. According to the official Claude Code documentation on hooks, PreToolUse events are designed specifically for this kind of policy enforcement.
When you’re building AI strategy and readiness, this pattern becomes your baseline. It’s how you move from “Claude can do anything” to “Claude can do anything within these guardrails.” For teams pursuing SOC 2 compliance or ISO 27001 certification, PreToolUse hooks are a control mechanism that auditors recognise and value.
Blocking Secrets in Commits {#blocking-secrets-commits}
The most common vulnerability: Claude writes code that includes hardcoded credentials. A developer runs the hook-enabled Claude Code session, and suddenly an AWS key is committed to the repository.
PreToolUse hooks solve this. You inspect every git commit before it happens and reject it if it contains patterns that look like secrets.
Pattern Detection Strategy
You need multiple detection layers:
- Regex patterns for common secrets: AWS keys, GCP service accounts, private keys, Stripe API keys
- Entropy analysis: Strings that look random (high entropy) in places they shouldn’t be
- Known secret repositories: Check against public lists of leaked credentials
- File extension rules:
.pem,.key,.envfiles shouldn’t be committed to main branches
Production Hook Recipe
Here’s a production-grade PreToolUse hook that blocks secrets in git commits:
{
"hooks": {
"PreToolUse": {
"function": "enforceSecretPolicy",
"config": {
"blockedPatterns": [
{
"name": "AWS Access Key",
"pattern": "AKIA[0-9A-Z]{16}",
"severity": "critical"
},
{
"name": "AWS Secret Key",
"pattern": "aws_secret_access_key\\s*=\\s*[A-Za-z0-9/+=]{40}",
"severity": "critical"
},
{
"name": "Private Key",
"pattern": "-----BEGIN (RSA|DSA|EC|OPENSSH|PGP) PRIVATE KEY",
"severity": "critical"
},
{
"name": "Stripe API Key",
"pattern": "sk_(live|test)_[A-Za-z0-9]{20,}",
"severity": "critical"
},
{
"name": "Database URL with Password",
"pattern": "postgresql://[^:]+:[^@]+@",
"severity": "critical"
},
{
"name": "GitHub Token",
"pattern": "ghp_[A-Za-z0-9_]{36,255}",
"severity": "critical"
},
{
"name": "High Entropy String",
"pattern": "entropy_score > 4.5",
"severity": "warning"
}
],
"allowedBranches": ["main", "develop"],
"exemptFiles": ["CHANGELOG.md", "docs/security-examples.md"],
"action": "block"
}
}
}
}
The hook implementation scans the commit diff:
function enforceSecretPolicy(tool, input, context) {
// Only intercept git commit operations
if (tool !== 'bash_execute' || !input.includes('git commit')) {
return null;
}
// Extract the commit message and staged changes
const commitContent = getGitDiff('--cached');
const patterns = context.config.blockedPatterns;
for (const pattern of patterns) {
const regex = new RegExp(pattern.pattern, 'gi');
const matches = commitContent.match(regex);
if (matches && pattern.severity === 'critical') {
return {
allowed: false,
reason: `Blocked: ${pattern.name} detected in commit. Pattern matches: ${matches.length} occurrence(s). This looks like a credential or secret. Remove it and try again.`
};
}
}
// Check file extensions
const stagedFiles = getGitStagedFiles();
for (const file of stagedFiles) {
if (file.match(/\.(pem|key|env|secret)$/i)) {
return {
allowed: false,
reason: `Blocked: File ${file} has a sensitive extension and should not be committed. Use .gitignore or environment variables instead.`
};
}
}
return null; // Allow the commit
}
When Claude tries to commit code with a hardcoded API key, it receives:
Blocked: AWS Access Key detected in commit. Pattern matches: 1 occurrence(s).
This looks like a credential or secret. Remove it and try again.
Claude then sees the error and can fix it—removing the key and using environment variables instead. This is deterministic policy enforcement at the point of execution.
For teams building agentic AI systems that interact with external services, this pattern is non-negotiable. When you’re pursuing AI readiness across your organisation, secret management through hooks becomes a baseline control. It’s also a control that SOC 2 auditors expect to see documented.
Enforcing Branch Naming Conventions {#enforcing-branch-naming}
Branch naming conventions are often documented but rarely enforced at the point of creation. Claude Code hooks let you enforce them automatically.
Common conventions:
- Feature branches:
feature/JIRA-123-short-description - Bug fixes:
bugfix/JIRA-456-issue-summary - Hotfixes:
hotfix/PROD-789-critical-issue - Release branches:
release/v1.2.3 - Experimental:
experiment/username-feature-name
Hook Configuration
{
"hooks": {
"PreToolUse": {
"function": "enforceBranchNaming",
"config": {
"rules": [
{
"pattern": "^(feature|bugfix|hotfix|release|experiment)/[a-z0-9-]+$",
"message": "Branch names must follow: feature/JIRA-123-description or hotfix/JIRA-456-summary",
"excludeBranches": ["main", "develop", "staging"]
},
{
"pattern": "^feature/[A-Z]+-[0-9]+-",
"message": "Feature branches must include JIRA ticket (e.g., feature/JIRA-123-description)",
"type": "feature"
}
],
"blockDirectPushToMain": true,
"requirePullRequest": true
}
}
}
}
The implementation:
function enforceBranchNaming(tool, input, context) {
// Intercept git branch creation and checkout
if (tool !== 'bash_execute') {
return null;
}
const branchMatch = input.match(/git\s+(?:checkout\s+-b|branch)\s+([\w\/-]+)/);
if (!branchMatch) {
return null;
}
const branchName = branchMatch[1];
const rules = context.config.rules;
const excludeBranches = context.config.excludeBranches || [];
// Skip validation for protected branches
if (excludeBranches.includes(branchName)) {
return null;
}
// Check against all rules
for (const rule of rules) {
const regex = new RegExp(rule.pattern);
if (!regex.test(branchName)) {
return {
allowed: false,
reason: `Branch name rejected: "${branchName}" does not match policy. ${rule.message}`
};
}
}
return null; // Allow branch creation
}
Now when Claude tries to create a branch named fix-thing or claude-test, the hook rejects it:
Branch name rejected: "fix-thing" does not match policy.
Branch names must follow: feature/JIRA-123-description or hotfix/JIRA-456-summary
Claude learns the convention and creates feature/JIRA-789-fix-thing instead.
This pattern is particularly valuable for teams scaling agentic AI across multiple projects. When you have Claude working on dozens of repositories, consistent branch naming becomes critical for tracking, auditing, and automation. It’s also a control that shows up in security audit checklists.
Gating Destructive Bash Operations {#gating-destructive-bash}
The most dangerous scenario: Claude executes a destructive command without warning. rm -rf /, database drops, production deployments, or clearing logs.
PreToolUse hooks let you create a whitelist of allowed destructive operations and block everything else.
Risk Categories
- Filesystem destruction:
rm -rf,shred, recursive deletes - Database operations:
DROP TABLE,TRUNCATE,DELETE FROMwithout WHERE clauses - Process termination:
kill -9,pkill, system shutdown - Network changes:
iptables,route, DNS modifications - Production deployment: Any command that modifies production infrastructure
Production Hook Recipe
{
"hooks": {
"PreToolUse": {
"function": "gateDestructiveOperations",
"config": {
"destructivePatterns": [
{
"name": "Recursive Delete",
"pattern": "rm\\s+(-r|-rf)\\s+",
"severity": "critical",
"requiresApproval": true,
"allowedPaths": ["./tmp", "./build", "./dist", "./.next"]
},
{
"name": "Shred/Wipe",
"pattern": "(shred|wipe|srm)\\s+",
"severity": "critical",
"requiresApproval": true
},
{
"name": "Drop Table",
"pattern": "DROP\\s+TABLE|TRUNCATE\\s+TABLE",
"severity": "critical",
"requiresApproval": true,
"allowedEnv": ["test", "staging"]
},
{
"name": "Delete Without Where",
"pattern": "DELETE\\s+FROM\\s+[^;]+;(?!.*WHERE)",
"severity": "critical",
"requiresApproval": true
},
{
"name": "Kill Process",
"pattern": "kill\\s+(-9|-KILL)\\s+",
"severity": "high",
"requiresApproval": true,
"allowedProcesses": ["node", "python"]
},
{
"name": "Production Deployment",
"pattern": "(deploy|release|push).*production",
"severity": "critical",
"requiresApproval": true,
"approvalChannels": ["slack", "pagerduty"]
}
],
"defaultAction": "block",
"auditLog": true
}
}
}
}
The implementation with approval flow:
function gateDestructiveOperations(tool, input, context) {
if (tool !== 'bash_execute') {
return null;
}
const patterns = context.config.destructivePatterns;
const currentEnv = process.env.NODE_ENV || 'development';
for (const pattern of patterns) {
const regex = new RegExp(pattern.pattern, 'gi');
if (regex.test(input)) {
// Check if this operation is allowed in the current environment
if (pattern.allowedEnv && !pattern.allowedEnv.includes(currentEnv)) {
return {
allowed: false,
reason: `Blocked: ${pattern.name} is not allowed in ${currentEnv} environment. Severity: ${pattern.severity}`
};
}
// Check if path is in allowed list
if (pattern.allowedPaths) {
const pathMatch = input.match(/rm\\s+(?:-r|-rf)\\s+([\w\/\.\-]+)/);
if (pathMatch && !pattern.allowedPaths.includes(pathMatch[1])) {
return {
allowed: false,
reason: `Blocked: Cannot delete ${pathMatch[1]}. Only these paths are allowed: ${pattern.allowedPaths.join(', ')}`
};
}
}
// If approval is required, request it
if (pattern.requiresApproval) {
const approvalToken = requestApproval({
operation: pattern.name,
command: input,
severity: pattern.severity,
channels: pattern.approvalChannels
});
if (!approvalToken) {
return {
allowed: false,
reason: `${pattern.name} requires explicit approval. Request sent to ${pattern.approvalChannels.join(', ')}. Waiting for approval...`
};
}
}
// Log the operation
if (context.config.auditLog) {
logDestructiveOperation({
timestamp: new Date(),
operation: pattern.name,
command: input,
environment: currentEnv,
approved: !!approvalToken
});
}
}
}
return null; // Allow safe operations
}
When Claude tries to run rm -rf /var/app, it gets blocked:
Blocked: Recursive Delete is not allowed in production environment. Severity: critical
If Claude tries to drop a production database table:
Blocked: Drop Table is not allowed in production environment. Severity: critical
You can perform this operation in test or staging environments.
If Claude tries something allowed but risky, like deleting the build directory:
Recursive Delete requires explicit approval. Request sent to slack, pagerduty.
Waiting for approval...
This is how you move from “we hope Claude doesn’t break production” to “Claude literally cannot break production without explicit approval.”
For teams modernising with agentic AI, this pattern is essential. When you’re running platform engineering projects or AI transformation initiatives, destructive operation gating becomes a baseline requirement. It’s also a control that PE firms and their portfolio companies expect to see when evaluating technology due diligence.
Real Production Configurations {#real-production-configurations}
Here’s the actual settings.json configuration that PADISO ships to mid-market clients. This is battle-tested across 50+ implementations.
Complete settings.json for Security-First Teams
{
"name": "Engineering Policy Enforcement",
"version": "1.0.0",
"description": "Production-grade Claude Code hooks for secret blocking, branch naming, and destructive operation gating",
"hooks": {
"PreToolUse": {
"enabled": true,
"functions": [
"enforceSecretPolicy",
"enforceBranchNaming",
"gateDestructiveOperations",
"validateSQLSyntax",
"checkFilePermissions"
],
"config": {
"secretPolicy": {
"enabled": true,
"blockedPatterns": [
{
"name": "AWS Access Key",
"pattern": "AKIA[0-9A-Z]{16}",
"severity": "critical",
"action": "block"
},
{
"name": "AWS Secret Key",
"pattern": "aws_secret_access_key\\s*=\\s*[A-Za-z0-9/+=]{40}",
"severity": "critical",
"action": "block"
},
{
"name": "Private Key",
"pattern": "-----BEGIN (RSA|DSA|EC|OPENSSH|PGP) PRIVATE KEY",
"severity": "critical",
"action": "block"
},
{
"name": "Stripe API Key",
"pattern": "sk_(live|test)_[A-Za-z0-9]{20,}",
"severity": "critical",
"action": "block"
},
{
"name": "GitHub Token",
"pattern": "ghp_[A-Za-z0-9_]{36,255}",
"severity": "critical",
"action": "block"
},
{
"name": "Slack Token",
"pattern": "xoxb-[0-9]{10,13}-[0-9]{10,13}-[A-Za-z0-9]{24,34}",
"severity": "critical",
"action": "block"
},
{
"name": "Database URL with Password",
"pattern": "(postgresql|mysql|mongodb)://[^:]+:[^@]+@",
"severity": "critical",
"action": "block"
}
],
"exemptFiles": [
"CHANGELOG.md",
"docs/security-examples.md",
"tests/fixtures/credentials.json"
],
"auditLog": true
},
"branchNaming": {
"enabled": true,
"rules": [
{
"pattern": "^(feature|bugfix|hotfix|release|experiment|chore|docs)/[a-z0-9-]+$",
"message": "Branch must follow: feature/JIRA-123-description or hotfix/JIRA-456-summary",
"excludeBranches": ["main", "develop", "staging", "production"]
},
{
"pattern": "^feature/[A-Z]+-[0-9]+-",
"message": "Feature branches must include JIRA ticket",
"type": "feature"
}
],
"blockDirectPushToMain": true,
"requirePullRequest": true,
"auditLog": true
},
"destructiveOperations": {
"enabled": true,
"patterns": [
{
"name": "Recursive Delete",
"pattern": "rm\\s+(-r|-rf)\\s+",
"severity": "critical",
"requiresApproval": true,
"allowedPaths": ["./tmp", "./build", "./dist", "./.next", "./node_modules"],
"allowedEnv": ["development", "test"]
},
{
"name": "Drop Table",
"pattern": "DROP\\s+TABLE|TRUNCATE\\s+TABLE",
"severity": "critical",
"requiresApproval": true,
"allowedEnv": ["test", "staging"]
},
{
"name": "Delete Without Where",
"pattern": "DELETE\\s+FROM\\s+[^;]+;(?!.*WHERE)",
"severity": "critical",
"requiresApproval": true,
"allowedEnv": ["test"]
},
{
"name": "Kill Process",
"pattern": "kill\\s+(-9|-KILL)\\s+",
"severity": "high",
"requiresApproval": true,
"allowedProcesses": ["node", "python", "npm"]
}
],
"defaultAction": "block",
"auditLog": true,
"approvalChannels": ["slack", "email"]
},
"sqlValidation": {
"enabled": true,
"validateSyntax": true,
"blockDangerousPatterns": [
"DELETE.*WHERE.*1.*=.*1",
"UPDATE.*SET.*WHERE.*1.*=.*1",
"DROP.*CASCADE"
]
},
"filePermissions": {
"enabled": true,
"restrictedExtensions": [".pem", ".key", ".env", ".secret"],
"restrictedPaths": ["/etc", "/root", "/var/log"]
}
}
},
"PostToolUse": {
"enabled": true,
"functions": ["auditLog", "notifyOnBlocks"],
"config": {
"auditLog": {
"enabled": true,
"destination": "./logs/claude-code-audit.json",
"retention": "90d",
"fields": [
"timestamp",
"tool",
"operation",
"allowed",
"reason",
"user",
"environment"
]
},
"notifications": {
"enabled": true,
"channels": {
"blocks": "slack",
"approvals": "slack",
"errors": "email"
},
"slackWebhook": "${SLACK_WEBHOOK_URL}",
"emailRecipients": ["security@company.com"]
}
}
}
},
"environment": {
"development": {
"strictness": "medium",
"requireApproval": false,
"auditLog": true
},
"staging": {
"strictness": "high",
"requireApproval": true,
"auditLog": true
},
"production": {
"strictness": "maximum",
"requireApproval": true,
"approvalChannels": ["slack", "pagerduty"],
"auditLog": true,
"blockAllDestructive": true
}
}
}
This configuration is designed to be:
- Environment-aware: Different rules for dev, staging, and production
- Audit-ready: Every blocked operation is logged with timestamp, user, and reason
- Approval-enabled: Risky operations can require explicit sign-off
- Flexible: Rules can be enabled/disabled per team without redeployment
Integration with CI/CD Pipelines {#integration-cicd}
Claude Code hooks don’t exist in isolation. They integrate with your CI/CD pipeline to create a complete enforcement layer.
When you’re building AI strategy and readiness across your organisation, the integration pattern matters. You need hooks to enforce policy during AI-assisted development, and you need CI/CD checks to catch anything that slips through.
According to the comprehensive guide on Claude Code hooks, the most robust teams layer multiple enforcement points. Here’s the pattern:
- Claude Code hooks (PreToolUse): Real-time enforcement during development
- Pre-commit hooks (git hooks): Client-side verification before commits
- CI/CD checks: Server-side validation before merge
- Security scanning: Automated secret detection, SAST, DAST
Integration Example: GitHub Actions
name: Enforce Engineering Policy
on:
pull_request:
types: [opened, synchronize]
jobs:
validate-branch-name:
runs-on: ubuntu-latest
steps:
- name: Check branch naming
run: |
BRANCH_NAME=${{ github.head_ref }}
if ! [[ $BRANCH_NAME =~ ^(feature|bugfix|hotfix|release|experiment)/[a-z0-9-]+$ ]]; then
echo "Branch name '$BRANCH_NAME' does not match policy"
exit 1
fi
scan-for-secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Scan for secrets
run: |
pip install detect-secrets
detect-secrets scan --all-files --force-use-all-plugins
if [ $? -ne 0 ]; then
echo "Secrets detected in commit"
exit 1
fi
validate-sql:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Validate SQL syntax
run: |
find . -name "*.sql" -type f | while read file; do
sqlparse --validate "$file"
if [ $? -ne 0 ]; then
echo "Invalid SQL in $file"
exit 1
fi
done
audit-destructive-ops:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check for destructive patterns
run: |
if git diff --name-only HEAD~1 | grep -E '\.(sh|bash)$'; then
git diff HEAD~1 | grep -E '(rm -rf|DROP TABLE|TRUNCATE|DELETE.*WHERE.*1.*=.*1)' && exit 1 || true
fi
This CI/CD layer catches anything that slips past Claude Code hooks—whether because the hook was disabled, misconfigured, or a developer bypassed it.
Common Pitfalls and Solutions {#common-pitfalls}
We’ve seen teams implement Claude Code hooks and hit predictable problems. Here are the solutions.
Pitfall 1: Hooks That Are Too Strict
Problem: Teams configure hooks that block legitimate operations. Developers disable them or work around them.
Solution: Start with audit mode (logging only, not blocking). Monitor for 2 weeks. Then enable blocking for the highest-severity patterns. Use environment-specific strictness—development can be lenient, production must be strict.
Pitfall 2: No Approval Workflow
Problem: Critical operations require approval, but there’s no clear process. Developers get blocked and don’t know what to do.
Solution: Implement an approval workflow with clear channels (Slack, email, PagerDuty). Approvals should be fast—5-minute SLA for non-production, 30-minute for production. Log who approved what, when.
Pitfall 3: Regex Patterns That Match False Positives
Problem: The secret detection regex matches legitimate strings (like test data or documentation examples).
Solution: Maintain an exemption list for specific files. Use entropy analysis as a secondary check, not primary. Review false positives weekly and refine patterns.
Pitfall 4: Insufficient Logging
Problem: A hook blocks an operation, but there’s no audit trail. When security questions arise, you can’t explain what happened.
Solution: Log every block, every approval, every allow. Include timestamp, user, operation, reason, environment. Retain logs for compliance (90 days minimum for SOC 2, 1 year for ISO 27001).
Pitfall 5: Hooks That Drift from Documentation
Problem: The actual hook rules don’t match what’s documented. Teams don’t understand what’s enforced.
Solution: Make settings.json the source of truth. Generate documentation from it automatically. Review quarterly with the team.
Scaling Hooks Across Teams {#scaling-hooks}
When you’re running platform engineering projects or modernising with agentic AI across multiple teams, scaling hooks becomes critical.
Here’s how to do it without creating chaos:
1. Centralised Configuration Repository
Store settings.json in a shared repository. Teams can inherit from it and override specific rules.
{
"extends": "https://github.com/company/engineering-policies/main/settings.json",
"overrides": {
"branchNaming": {
"rules": [
{
"pattern": "^(feature|bugfix)/[A-Z]+-[0-9]+-",
"message": "Feature branches must include JIRA ticket"
}
]
}
}
}
2. Version Hooks
Version your hooks like you version your code. When you update a hook, increment the version and communicate changes to teams.
{
"version": "2.1.0",
"changelog": {
"2.1.0": "Added Stripe API key detection, improved entropy analysis",
"2.0.0": "Introduced approval workflows for destructive operations",
"1.0.0": "Initial release"
}
}
3. Team-Specific Rules
Some teams (e.g., data engineering) might need different rules than others (e.g., frontend). Support this with team namespaces.
{
"hooks": {
"PreToolUse": {
"teams": {
"platform-engineering": {
"strictness": "maximum",
"blockAllDestructive": true
},
"data-engineering": {
"strictness": "high",
"allowDataDeletion": ["test", "staging"],
"requireApprovalFor": ["production"]
},
"frontend": {
"strictness": "medium",
"skipFilePermissionChecks": true
}
}
}
}
}
4. Dashboard for Monitoring
Build a dashboard that shows:
- Hooks triggered per team, per day
- Most common blocks
- Approval requests and resolution time
- False positive rate
This helps you tune rules and identify teams that need training.
For organisations pursuing AI readiness, this monitoring becomes part of your AI governance framework. For teams pursuing SOC 2 or ISO 27001 certification, this dashboard becomes evidence of control implementation and effectiveness.
Next Steps and Implementation {#next-steps}
You now have production-grade recipes for Claude Code hooks. Here’s how to implement them.
Week 1: Audit Mode
- Copy the
settings.jsonfrom this guide - Set all hooks to
auditLog: true,action: log(don’t block yet) - Deploy to development environment
- Monitor logs for false positives and tune patterns
Week 2: Soft Enforcement
- Enable blocking for highest-severity patterns (secrets, destructive ops)
- Keep branch naming in audit mode
- Deploy to staging
- Train team on new rules
Week 3: Full Enforcement
- Enable all blocks
- Set up approval workflow
- Deploy to production
- Monitor approval request volume and SLA
Week 4: Integration & Scaling
- Integrate with CI/CD pipeline
- Build monitoring dashboard
- Document rules for team
- Plan rollout to other teams
Implementation Checklist
- Copy
settings.jsonto your repository - Implement hook functions (enforceSecretPolicy, enforceBranchNaming, gateDestructiveOperations)
- Test with Claude Code on a non-critical project
- Set up audit logging to a secure location
- Configure Slack/email notifications
- Train team on new policies
- Document exceptions and approval process
- Schedule weekly review of logs and false positives
- Integrate with CI/CD pipeline
- Plan rollout to other teams
When to Get Help
If you’re implementing this at scale—across multiple teams, multiple repositories, or in a highly regulated environment—consider working with a partner who specialises in AI governance and platform engineering.
At PADISO, we’ve implemented these patterns for 50+ clients across startups, mid-market companies, and enterprises. We understand the nuances of agentic AI vs traditional automation, the security requirements for SOC 2 compliance and ISO 27001 certification, and how to scale AI safely across your organisation.
If you’re building AI strategy and readiness, modernising with agentic AI, or pursuing compliance certification, we can help you implement not just hooks, but a complete AI governance framework.
Conclusion
Claude Code hooks, specifically the PreToolUse pattern, give you deterministic control over what Claude can and cannot do. You’re not trying to outsmart AI; you’re setting boundaries that AI respects.
The recipes in this guide—blocking secrets, enforcing branch naming, gating destructive operations—are production-tested patterns used by teams shipping agentic AI systems safely. They work because they enforce policy at the point of execution, before anything can go wrong.
Start with audit mode. Tune your patterns. Then enable blocking. Scale across teams with centralised configuration. Monitor relentlessly.
This is how you move from “we hope Claude doesn’t break things” to “Claude literally cannot break things without explicit approval.” It’s how you build AI systems that auditors trust, teams rely on, and businesses can scale confidently.
The technology is mature. The patterns are proven. The only thing left is implementation.
For teams at PADISO-partnered companies, we’re here to help with the full journey—from AI strategy and readiness through implementation, scaling, and compliance. For everyone else, take these recipes and ship them. Your future self will thank you when the first blocked secret saves your company from a credential leak.
Start this week. Monitor obsessively. Scale with confidence.