Skip to main content
Hooks are event-driven automations that fire before or after tool executions. They enforce code quality, catch mistakes early, and automate repetitive checks without manual intervention.

How Hooks Work

1

User Request

You make a request: “Add authentication middleware”
2

Claude Picks Tool

Claude decides to use the Edit tool to modify a file
3

PreToolUse Hook Fires

Before the edit executes, PreToolUse hooks run:
  • Doc file warning checks if it’s a non-standard .md file
  • Strategic compact suggests manual compaction if needed
4

Tool Executes

The Edit tool modifies the file
5

PostToolUse Hook Fires

After the edit completes, PostToolUse hooks run:
  • Prettier auto-formats the file
  • TypeScript check runs tsc --noEmit
  • Console.log warning scans for debug statements

Hook Types

ECC uses five hook event types:

PreToolUse

When: Before a tool executesCan: Block execution (exit code 2) or warn (stderr)Use cases:
  • Block dev servers outside tmux
  • Warn about non-standard file creation
  • Secret detection in prompts
  • Rate limiting validation

PostToolUse

When: After a tool completesCan: Analyze output, trigger followup actionsCannot: Block execution (already completed)Use cases:
  • Auto-format code after edits
  • Run type checks
  • Log PR URLs after creation
  • Background build analysis

Stop

When: After each Claude responseUse cases:
  • Audit all modified files
  • Extract session patterns
  • Update metrics

SessionStart

When: Session beginsUse cases:
  • Load previous context
  • Detect package manager
  • Initialize session state

SessionEnd

When: Session endsUse cases:
  • Persist session state
  • Extract patterns for continuous learning
  • Save checkpoints

PreCompact

When: Before context compactionUse cases:
  • Save state before compaction
  • Log compaction event
  • Archive context

Hooks in ECC

PreToolUse Hooks

Matcher: BashBehavior: Blocks npm run dev, pnpm dev, yarn dev, bun run dev outside tmuxExit code: 2 (blocks execution)Why: Ensures you can access server logs later via tmux attachExample:
[Hook] BLOCKED: Dev server must run in tmux for log access
[Hook] Use: tmux new-session -d -s dev "npm run dev"
[Hook] Then: tmux attach -t dev
Matcher: BashBehavior: Warns about long-running commands (tests, builds, docker)Exit code: 0 (warns only)Commands matched:
  • npm install, npm test
  • cargo build, make
  • docker, pytest, vitest, playwright
Example:
[Hook] Consider running in tmux for session persistence
[Hook] tmux new -s dev  |  tmux attach -t dev
Matcher: BashBehavior: Reminds to review changes before git pushExit code: 0 (warns only)Example:
[Hook] Review changes before push...
[Hook] Continuing with push (remove this hook to add interactive review)
Matcher: WriteBehavior: Warns about non-standard .md/.txt filesExit code: 0 (warns only)Allowed files:
  • README.md, CLAUDE.md, CONTRIBUTING.md
  • CHANGELOG.md, LICENSE, SKILL.md
  • Files in docs/ or skills/ directories
Example:
[Hook] Unusual documentation file detected: notes.md
[Hook] Standard docs belong in docs/ or README.md
Matcher: Edit|WriteBehavior: Suggests manual /compact every ~50 tool callsExit code: 0 (warns only)Example:
[Hook] Consider manual /compact - you've done ~50 operations
[Hook] Good time: after research, before implementation

PostToolUse Hooks

Matcher: BashBehavior: Logs PR URL after gh pr createExample:
[Hook] PR created: https://github.com/user/repo/pull/123
[Hook] Review: gh pr view 123 --web
Matcher: BashBehavior: Async analysis after build commandsAsync: Yes (doesn’t block)Timeout: 30 secondsCommands matched: npm run build, yarn build, tsc, webpack
Matcher: EditBehavior: Runs Prettier on JS/TS files after editsFiles matched: .js, .jsx, .ts, .tsxExample:
// After editing file.ts, automatically runs:
prettier --write file.ts
Matcher: EditBehavior: Runs tsc --noEmit after editing .ts/.tsx filesExample:
[Hook] Running TypeScript check...
[Hook] ✓ No type errors found
Matcher: EditBehavior: Warns about console.log statements in edited filesExample:
[Hook] console.log detected on line 42
[Hook] Remove before committing

Lifecycle Hooks

Event: SessionStartActions:
  • Loads previous session context from .claude/session-state.json
  • Detects package manager (npm, pnpm, yarn, bun)
  • Initializes continuous learning observer
Example:
[Hook] Loaded session state from previous run
[Hook] Package manager: pnpm
Event: PreCompactActions:
  • Saves current state to .claude/pre-compact-state.json
  • Archives context for potential recovery
Example:
[Hook] Saving state before compaction...
[Hook] State saved to .claude/pre-compact-state.json
Event: Stop (after each response)Actions:
  • Scans all modified files for console.log
  • Reports findings with file paths and line numbers
Example:
[Hook] Console.log audit:
[Hook] src/lib/utils.ts:15
[Hook] src/components/Button.tsx:42
Event: SessionEndActions:
  • Persists session state to .claude/session-state.json
  • Evaluates session for extractable patterns
  • Updates continuous learning instincts
Example:
[Hook] Session state saved
[Hook] 3 patterns extracted for continuous learning

Hook Input Schema

Hooks receive JSON on stdin:
interface HookInput {
  tool_name: string          // "Bash", "Edit", "Write", "Read", etc.
  tool_input: {
    command?: string         // Bash: the command being run
    file_path?: string       // Edit/Write/Read: target file
    old_string?: string      // Edit: text being replaced
    new_string?: string      // Edit: replacement text
    content?: string         // Write: file content
  }
  tool_output?: {            // PostToolUse only
    output?: string          // Command/tool output
  }
}

Exit Codes

Hooks communicate via exit codes:
Exit CodeMeaningEffect
0SuccessContinue execution, show warnings (stderr)
2BlockStop tool execution (PreToolUse only)
OtherErrorLogged but does not block

Writing Custom Hooks

Hooks are Node.js scripts that read JSON from stdin:
// my-hook.js
let data = ''
process.stdin.on('data', chunk => data += chunk)
process.stdin.on('end', () => {
  const input = JSON.parse(data)
  
  // Access tool info
  const toolName = input.tool_name        // "Edit", "Bash", etc.
  const toolInput = input.tool_input      // Tool-specific parameters
  const toolOutput = input.tool_output    // PostToolUse only
  
  // Warn (non-blocking): write to stderr
  if (someCondition) {
    console.error('[Hook] Warning message shown to Claude')
  }
  
  // Block (PreToolUse only): exit with code 2
  if (shouldBlock) {
    console.error('[Hook] BLOCKED: Reason for blocking')
    process.exit(2)
  }
  
  // Always output original data to stdout
  console.log(data)
})

Add to hooks.json

{
  "PreToolUse": [
    {
      "matcher": "Edit",
      "hooks": [
        {
          "type": "command",
          "command": "node /path/to/my-hook.js"
        }
      ],
      "description": "Custom edit validation"
    }
  ]
}

Common Hook Recipes

Warn About TODO Comments

{
  "matcher": "Edit",
  "hooks": [{
    "type": "command",
    "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const ns=i.tool_input?.new_string||'';if(/TODO|FIXME|HACK/.test(ns)){console.error('[Hook] New TODO/FIXME added - consider creating an issue')}console.log(d)})\""
  }],
  "description": "Warn when adding TODO/FIXME comments"
}

Block Large File Creation

{
  "matcher": "Write",
  "hooks": [{
    "type": "command",
    "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines ('+lines+' lines)');console.error('[Hook] Split into smaller, focused modules');process.exit(2)}console.log(d)})\""
  }],
  "description": "Block creation of files larger than 800 lines"
}

Auto-Format Python with Ruff

{
  "matcher": "Edit",
  "hooks": [{
    "type": "command",
    "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/\\.py$/.test(p)){const{execFileSync}=require('child_process');try{execFileSync('ruff',['format',p],{stdio:'pipe'})}catch(e){}}console.log(d)})\""
  }],
  "description": "Auto-format Python files with ruff after edits"
}

Require Test Files for New Source

{
  "matcher": "Write",
  "hooks": [{
    "type": "command",
    "command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/src\\/.*\\.(ts|js)$/.test(p)&&!/\\.test\\.|\\.spec\\./.test(p)){const testPath=p.replace(/\\.(ts|js)$/,'.test.$1');if(!fs.existsSync(testPath)){console.error('[Hook] No test file found for: '+p);console.error('[Hook] Expected: '+testPath);console.error('[Hook] Consider writing tests first (/tdd)')}}console.log(d)})\""
  }],
  "description": "Remind to create tests when adding new source files"
}

Cross-Platform Compatibility

All ECC hooks use Node.js for maximum compatibility across Windows, macOS, and Linux. Avoid bash-specific syntax.
Windows Users: Hooks using bash or shell-specific features may not work. ECC hooks use node -e for portability.

Hook Best Practices

Blocking hooks (exit 2) should only be used for critical issues like security violations or dev server misconfigurations. Most hooks should warn (stderr) instead.
Hooks run synchronously (except async hooks). Slow hooks delay tool execution. Keep hook logic under 100ms.
Hooks must console.log(data) to pass the original input through. Forgetting this breaks the tool chain.
Build analysis, background jobs, or slow checks should use "async": true to avoid blocking.
Verify that blocking hooks (exit 2) actually block tool execution, and warning hooks (exit 0) don’t.

Disabling Hooks

To disable a hook, override it in ~/.claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [],
        "description": "Override: allow all .md file creation"
      }
    ]
  }
}

Next Steps

Explore Rules

Learn about always-follow guidelines

Hook Reference

Full catalog of all hooks

Memory Persistence

Session lifecycle hooks for context management

Custom Hooks

Step-by-step guide to building your own hooks