I have 160 Claude Code hooks. The 5 that pay rent every day.
Current Situation Analysis
AI coding assistants operate on probabilistic compliance. When developers define constraints through markdown rules, system prompts, or inline instructions, they are creating suggestions. The model interprets these suggestions based on context window pressure, token probability distributions, and competing instructions. In production environments, this architectural gap manifests as silent rule violations, uncontrolled execution costs, and credential exposure.
The industry has historically treated LLM constraints as documentation rather than enforcement mechanisms. This mindset persists because prompt engineering feels lightweight and immediate. However, as agent loops grow more autonomous, the cost of relying on probabilistic compliance compounds. A single headless execution misfire can trigger unexpected billing. A missed credential in a git add sequence can pollute commit history before CI/CD pipelines intercept it. Unbounded asset growth (rules, hooks, skills) degrades context window efficiency over time.
Data from mature deployments reveals a clear pattern: teams that treat hooks as boundary enforcement rather than optional utilities see measurable reductions in operational friction. One production setup tracks 160 active hooks, 95 rules, and 75 skills. The billing architecture shift on June 15, 2026, which decoupled Agent SDK billing from standard subscriptions, further accelerated this shift. Headless claude -p invocations now bill against the SDK tier, making uncontrolled automation financially risky. Hooks intercept tool calls before execution, converting suggestions into hard constraints. This architectural change mirrors the DevOps transition from manual deployment checklists to pipeline-enforced gates.
WOW Moment: Key Findings
The fundamental shift occurs when constraints move from the prompt layer to the execution layer. The following comparison illustrates the operational impact of hook-based enforcement versus traditional rule-based approaches.
| Approach | Compliance Rate | Detection Latency | Cost Exposure | Maintenance Overhead |
|---|---|---|---|---|
| Markdown Rules / Prompts | ~65-78% (context-dependent) | Post-execution (requires review) | High (headless calls bill immediately) | Low initially, degrades with rule count |
| Hook Enforcement | ~99%+ (boundary-gated) | Pre-execution (intercepted) | Near-zero (blocked before billing) | Moderate upfront, scales linearly |
This finding matters because it redefines how teams architect AI agent workflows. Instead of debugging why a model "forgot" a constraint, engineers design systems where the constraint cannot be bypassed. The hook layer acts as a deterministic filter between the agent's intent and the host environment. This enables predictable cost modeling, safer automation, and consistent output formatting without relying on model memory or prompt stability.
Core Solution
Claude Code hooks execute as standalone scripts that receive JSON payloads via standard input. The execution model relies on three core concepts: events (when the hook triggers), matchers (which tool or event activates the hook), and exit codes (how the hook communicates approval or rejection). A PreToolUse hook intercepts tool calls before execution, while SessionStart runs when a new session initializes. Returning exit code 2 signals rejection; exit code 0 allows continuation.
1. Cost Gate: Intercepting Headless Execution
The billing architecture change on June 15, 2026, requires explicit controls around headless invocations. This hook monitors Bash tool calls and blocks patterns that trigger SDK billing.
#!/usr/bin/env bash
# @event: PreToolUse
# @matcher: Bash
set -euo pipefail
# Read JSON payload from stdin
INPUT_STREAM=$(cat)
TARGET_COMMAND=$(echo "$INPUT_STREAM" | jq -r '.tool_input.command // empty')
# Validate command extraction
if [[ -z "$TARGET_COMMAND" ]]; then
exit 0
fi
# Block headless invocation patterns
if echo "$TARGET_COMMAND" | grep -qE '\bclaude[[:space:]]+(-p|--print)\b'; then
echo "[COST-GATE] Blocked headless invocation. Post-2026-06-15 billing applies to SDK tier." >&2
echo "[COST-GATE] Switch to interactive subagent mode or adjust billing configuration." >&2
exit 2
fi
exit 0
Architecture Rationale: Using set -euo pipefail prevents silent failures. The jq extraction with // empty handles missing fields gracefully. The regex uses word boundaries to avoid false positives on commands like claude-something. Exit code 2 enforces rejection at the tool boundary.
2. Style Enforcer: Preventing AI Punctuation Artifacts
AI-generated content frequently introduces typographic artifacts like em dashes, which degrade readability in technical documentation. This hook intercepts Write and Edit operations targeting content files.
#!/usr/bin/env bash
# @event: PreToolUse
# @matcher: Write|Edit
set -euo pipefail
INPUT_STREAM=$(cat)
TARGET_PATH=$(echo "$INPUT_STREAM" | jq -r '.tool_input.file_path // empty')
CONTENT_BLOCK=$(echo "$INPUT_STREAM" | jq -r '.tool_input.content // .tool_input.new_string // empty')
# Skip non-content files
case "$TARGET_PATH" in
*.md|*.txt|*.rst|*.adoc) ;;
*) exit 0 ;;
esac
# Detect em dash (UTF-8: E2 80 94)
if [[ -n "$CONTENT_BLOCK" ]] && echo "$CONTENT_BLOCK" | grep -qP '\xe2\x80\x94'; then
echo "[STYLE-GATE] Em dash detected in $TARGET_PATH. Replace with standard punctuation." >&2
echo "[STYLE-GATE] Use periods, commas, or hyphens for technical documentation." >&2
exit 2
fi
exit 0
Architecture Rationale: File extension filtering prevents unnecessary processing on code or config files. The grep -P flag enables Perl-compatible regex for precise UTF-8 byte matching. The hook fails fast on non-content paths, preserving execution speed.
3. Credential Shield: Pre-Commit Secret Interception
Traditional pre-push hooks catch secrets after they enter the local repository. This hook operates earlier, intercepting git add commands before staging occurs.
#!/usr/bin/env bash
# @event: PreToolUse
# @matcher: Bash
set -euo pipefail
INPUT_STREAM=$(cat)
TARGET_COMMAND=$(echo "$INPUT_STREAM" | jq -r '.tool_input.command // empty')
# Define credential patterns
CREDENTIAL_REGEX='\.env($|\.)|credentials\.json|\.pem$|\.key$|service-account.*\.json|\.secret$'
# Check for git add operations targeting sensitive files
if echo "$TARGET_COMMAND" | grep -qE "git[[:space:]]+add.*($CREDENTIAL_REGEX)"; then
# Check for explicit override flag
if echo "$TARGET_COMMAND" | grep -qF '--allow-secret'; then
echo "[CRED-SHIELD] Override acknowledged via --allow-secret flag." >&2
exit 0
fi
echo "[CRED-SHIELD] Blocked staging of credential file." >&2
echo "[CRED-SHIELD] Add to .gitignore or use environment variable injection." >&2
exit 2
fi
exit 0
Architecture Rationale: The regex covers common credential file naming conventions. The explicit override mechanism (--allow-secret) prevents workflow paralysis during legitimate migrations. Early interception keeps secrets out of .git/objects, reducing cleanup complexity.
4. State Router: Session Initialization Awareness
Long-running development sessions benefit from passive state synchronization. This hook aggregates environmental metrics and surfaces them at session start.
#!/usr/bin/env bash
# @event: SessionStart
set -euo pipefail
CACHE_DIR="${HOME}/.claude/cache"
REPO_PATH="${HOME}/projects/agent-os"
# Aggregate pending items
PENDING_DOCS=$(find "$CACHE_DIR/docs-pending" -type f 2>/dev/null | wc -l | tr -d ' ')
SECURITY_ALERTS=$(find "$CACHE_DIR/security" -type f 2>/dev/null | wc -l | tr -d ' ')
DRIFT_COUNT=$(cd "$REPO_PATH" && git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
# Output structured summary
[[ "$PENDING_DOCS" -gt 0 ]] && echo "[STATE] Pending documentation: $PENDING_DOCS items"
[[ "$SECURITY_ALERTS" -gt 0 ]] && echo "[STATE] Security advisories: $SECURITY_ALERTS flags"
[[ "$DRIFT_COUNT" -gt 0 ]] && echo "[STATE] Repository drift: $DRIFT_COUNT uncommitted changes"
exit 0
Architecture Rationale: Using find with 2>/dev/null prevents errors from missing directories. tr -d ' ' normalizes wc output across platforms. The hook runs asynchronously to session initialization, providing context without blocking startup.
5. Asset Cap: Preventing Unbounded Configuration Growth
Evolving setups accumulate rules, hooks, and skills. Without constraints, configuration bloat degrades context window efficiency and increases maintenance overhead. This hook enforces hard limits on asset classes.
#!/usr/bin/env bash
# @event: PreToolUse
# @matcher: Write
set -euo pipefail
INPUT_STREAM=$(cat)
TARGET_PATH=$(echo "$INPUT_STREAM" | jq -r '.tool_input.file_path // empty')
# Define asset caps
declare -A ASSET_CAPS=(
[rules]=95
[hooks]=160
[skills]=75
)
# Match asset directories
for ASSET_TYPE in "${!ASSET_CAPS[@]}"; do
if [[ "$TARGET_PATH" == *".claude/$ASSET_TYPE/"* ]]; then
CURRENT_COUNT=$(find "${HOME}/.claude/$ASSET_TYPE" -name "*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
MAX_CAP=${ASSET_CAPS[$ASSET_TYPE]}
# Block if at cap and creating new file
if [[ "$CURRENT_COUNT" -ge "$MAX_CAP" ]] && [[ ! -f "$TARGET_PATH" ]]; then
echo "[ASSET-CAP] $ASSET_TYPE limit reached ($CURRENT_COUNT/$MAX_CAP)." >&2
echo "[ASSET-CAP] Consolidate existing assets before adding new ones." >&2
exit 2
fi
fi
done
exit 0
Architecture Rationale: Associative arrays enable scalable cap management. The find command counts only markdown files, ignoring temporary or backup files. The check [[ ! -f "$TARGET_PATH" ]] allows updates to existing assets while blocking new additions at capacity.
Pitfall Guide
1. Silent Hook Failures
Explanation: Hooks that don't write to stderr fail invisibly. Developers assume the hook executed successfully when it actually crashed or returned early.
Fix: Always route diagnostic output to stderr using >&2. Implement explicit success/failure logging. Test hooks with malformed payloads to verify error paths.
2. Overly Broad Matchers
Explanation: Using Bash or Write without path or command filtering causes false positives. Legitimate operations get blocked, breaking developer workflows.
Fix: Add granular filtering before enforcement. Use regex word boundaries, file extension checks, or command prefix validation. Maintain an explicit allowlist for known safe patterns.
3. Performance Degradation in Hot Paths
Explanation: Heavy regex engines, external tool calls, or network requests in PreToolUse hooks increase latency. Since hooks run synchronously before tool execution, slow hooks degrade the entire agent loop.
Fix: Keep hooks lightweight. Prefer built-in shell utilities over external binaries. Cache expensive lookups. Set execution timeouts using timeout command if available.
4. Hook Crash Loops
Explanation: Missing dependencies (e.g., jq not installed, incorrect permissions) cause hooks to fail repeatedly. The agent may retry or enter degraded states.
Fix: Add dependency validation at the top of each hook. Use command -v jq >/dev/null 2>&1 || exit 0 to fail gracefully. Version control hook dependencies alongside configuration.
5. Race Conditions in File Counting
Explanation: Concurrent writes or parallel agent threads can cause file count checks to return stale values. This leads to cap violations or missed blocks.
Fix: Use atomic operations where possible. Implement file locking with flock for critical sections. Accept eventual consistency for non-critical metrics like asset caps.
6. Hardcoded Environment Paths
Explanation: Assuming ~/.claude/ or specific repository paths breaks across machines, CI environments, or containerized setups.
Fix: Use environment variables or configuration files for path resolution. Fall back to standard XDG directories. Document required environment variables in hook headers.
7. Missing Fallback Guidance
Explanation: Blocking an operation without explaining the alternative frustrates users and encourages hook bypassing.
Fix: Always provide actionable remediation steps in stderr output. Suggest specific commands, configuration changes, or workflow adjustments. Maintain a hook documentation registry.
Production Bundle
Action Checklist
- Validate JSON payload parsing: Test each hook with malformed, empty, and edge-case payloads to ensure graceful degradation.
- Implement stderr logging: Route all diagnostic output to
stderrand verify visibility in agent logs. - Set up hook linting: Create a CI pipeline that validates hook syntax, permissions, and matcher configurations before deployment.
- Monitor execution latency: Instrument hooks with timing metrics to detect performance regressions in the agent loop.
- Version control hooks: Store hooks in a dedicated repository with semantic versioning and changelog tracking.
- Document exit codes: Maintain a registry of hook exit codes and their meanings for team onboarding.
- Review caps quarterly: Audit asset limits (rules, hooks, skills) and adjust based on actual usage patterns and context window constraints.
- Test override mechanisms: Verify that explicit bypass flags work correctly and log all override attempts for audit trails.
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|---|---|---|
| Enforcing coding style or documentation formatting | Hook Enforcement (PreToolUse on Write/Edit) | Deterministic boundary prevents AI artifacts before they enter the codebase | Low (prevents rework) |
| Preventing headless execution cost leakage | Hook Enforcement (PreToolUse on Bash) | Blocks billing-triggering commands before SDK invocation | High (direct cost avoidance) |
| Catching credentials before commit history pollution | Hook Enforcement (PreToolUse on Bash) | Intercepts at staging phase, avoiding git history cleanup | Medium (reduces remediation effort) |
| Surfacing environment state or pending tasks | SessionStart Hook | Passive aggregation provides context without blocking initialization | Low (operational efficiency) |
| Managing configuration bloat or context window pressure | Hook Enforcement (PreToolUse on Write) | Hard caps force consolidation, maintaining agent performance | Medium (sustains long-term efficiency) |
| Complex multi-step validation requiring external APIs | CI/CD Pipeline | Hooks lack network reliability and async capabilities; pipelines handle retries and state | Variable (infrastructure dependent) |
Configuration Template
#!/usr/bin/env bash
# =============================================================================
# Hook: [NAME]
# Event: [EVENT_TYPE]
# Matcher: [MATCHER_PATTERN]
# Purpose: [BRIEF_DESCRIPTION]
# =============================================================================
set -euo pipefail
# Configuration
HOOK_NAME="[NAME]"
LOG_PREFIX="[HOOK_NAME]"
PAYLOAD=$(cat)
# Dependency validation
command -v jq >/dev/null 2>&1 || { echo "[$LOG_PREFIX] Missing jq dependency. Exiting gracefully." >&2; exit 0; }
# Payload extraction
EXTRACTED_VALUE=$(echo "$PAYLOAD" | jq -r '.tool_input.[FIELD] // empty')
# Validation logic
if [[ -z "$EXTRACTED_VALUE" ]]; then
exit 0
fi
# Enforcement condition
if [[ "$EXTRACTED_VALUE" =~ [PATTERN] ]]; then
echo "[$LOG_PREFIX] Condition met. Blocking execution." >&2
echo "[$LOG_PREFIX] Remediation: [ACTIONABLE_GUIDANCE]" >&2
exit 2
fi
# Success path
exit 0
Quick Start Guide
- Initialize the hooks directory: Create
~/.claude/hooks/and ensure it has execute permissions. Add a.gitkeepfile to track the directory in version control. - Deploy the boilerplate: Copy the configuration template into a new file (e.g.,
cost-gate.sh). Replace placeholder values with your specific event, matcher, and validation logic. - Test with mock payloads: Create a test script that pipes sample JSON into the hook using
echo '{"tool_input":{"command":"claude -p test"}}' | ./cost-gate.sh. Verify exit codes and stderr output. - Register in Claude Code config: Add the hook path to your
~/.claude/settings.jsonunder the appropriate event and matcher configuration. Validate syntax withjq . ~/.claude/settings.json. - Verify in session: Start a new Claude Code session and trigger the target tool. Monitor the terminal for hook output. Check
~/.claude/logs/for execution traces and latency metrics.
Mid-Year Sale β Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all tutorials.
Sign In / Register β Start Free Trial7-day free trial Β· Cancel anytime Β· 30-day money-back
