How to Configure Hooks for Automated Workflows in ECC: A Complete Guide

Everything Claude Code (ECC) provides an event-driven hook system that runs custom Node.js commands automatically before or after Claude Code tool invocations, enabling automated quality gates, safety checks, and workflow enforcement without manual intervention.

The open-source affaan-m/ECC repository ships with a declarative hook framework that intercepts Claude Code operations at five distinct lifecycle phases. By configuring hooks in hooks/hooks.json, you can automate repetitive checks, block dangerous commands, and persist session state across your development workflow.

Understanding the Hook Architecture

ECC implements a JSON-driven hook system validated against the official Claude Code settings schema. The master configuration resides in hooks/hooks.json and defines matchers, commands, and execution phases.

Hook Lifecycle Phases

The system fires hooks during five specific execution windows:

  • PreToolUse: Executes immediately before tools like Bash, Edit, or Write run. Use this phase to block dangerous commands or enforce policy violations.
  • PostToolUse: Runs right after tool completion. Ideal for quality gates, logging PR URLs, or detecting design drift.
  • PreCompact: Fires before Claude compacts session context. Use for stashing pending edits or persisting transient state.
  • Stop: Triggers after every Claude response, even when no tool is used. Perfect for batch formatting, type-checking, or audit logging.
  • SessionStart / SessionEnd: Bookend phases for loading prior context, detecting package managers, or emitting lifecycle analytics.

Configuration Schema

Each hook entry in hooks/hooks.json requires three components:

  1. matcher: A glob-style pattern selecting target tools (e.g., Bash|Write|Edit)
  2. hooks: An array of command objects executing as Node.js one-liners
  3. description: Human-readable text for diagnostics

Commands receive the tool's JSON payload on stdin and must echo the same JSON to stdout. Exit code 2 blocks execution in PreToolUse hooks, while code 0 allows continued operation.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node path/to/script.js",
            "description": "Block dangerous commands"
          }
        ]
      }
    ]
  }
}

Installing and Enabling Hooks

You must install hooks via ECC's provided installer rather than manually copying JSON files. The installer rewrites paths in hooks.json to point at your Claude config directory (~/.claude).

Install the hooks runtime using the appropriate script for your platform:


# Linux/macOS

bash ./install.sh --target claude --modules hooks-runtime

# Windows PowerShell

pwsh -File .\install.ps1 --target claude --modules hooks-runtime

After installation, the resolved configuration appears at ~/.claude/hooks/hooks.json. Manual copying of raw JSON into ~/.claude/settings.json will break path resolution and cause hook failures.

Core Hook Categories

ECC ships with predefined hook groups targeting specific automation needs.

PreToolUse: Safety and Governance

These hooks enforce guardrails before tools execute:

  • pre:bash:dispatcher: Consolidates quality checks, tmux warnings, and GateGuard validation for Bash commands
  • pre:write:doc-file-warning: Warns when creating non-standard markdown files (e.g., stray .txt files)
  • pre:config-protection: Blocks modifications to linter or formatter configs, forcing code fixes instead of configuration changes
  • pre:governance-capture: Captures secrets or policy violations when ECC_GOVERNANCE_CAPTURE=1 is enabled

PostToolUse: Automated Quality Gates

Post-execution hooks analyze completed operations:

  • post:bash:dispatcher: Logs PR URLs and runs async build analysis
  • post:quality-gate: Executes fast lint and test checks on edited files
  • post:edit:design-quality-check: Warns when UI edits appear "template-like" or generic
  • post:edit:accumulator: Collects edited paths for batch processing during the Stop phase

Stop: Batch Operations

Stop-phase hooks run once per response to avoid per-edit overhead:

  • stop:format-typecheck: Runs Prettier or Biome plus tsc --noEmit on all files edited during the current response
  • stop:check-console-log: Scans modified files for stray console.log statements
  • stop:session-end: Persists session transcripts and metric aggregates

Session Lifecycle Hooks

  • session:start: Detects active package managers (npm, pnpm, yarn) and loads persisted context
  • session:end:marker: Emits lifecycle markers for downstream analytics

Customizing Your Hook Configuration

ECC provides multiple methods to tailor hook behavior without modifying core files.

Disabling Specific Hooks

Edit the installed ~/.claude/hooks/hooks.json to remove unwanted entries. Alternatively, override selectively in ~/.claude/settings.json by providing empty hook arrays:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [],
        "description": "Override: allow all .md file creation"
      }
    ]
  }
}

Runtime Environment Controls

Control hook profiles without touching JSON configuration using environment variables:

export ECC_HOOK_PROFILE=strict        # Options: minimal, standard, strict

export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck"
export ECC_GATEGUARD=off              # Disable GateGuard fact-forcing

export ECC_SESSION_START_MAX_CHARS=4000  # Limit startup context injection

Set ECC_GOVERNANCE_CAPTURE=1 to enable secret detection and policy violation capturing.

Creating Custom Hooks

Implement custom automation by writing Node.js scripts that read from stdin and write to stdout. The script receives the tool's JSON payload and must return it unmodified to continue execution.

Example hook blocking destructive rm -rf / commands:

// danger-guard.js
let data = '';
process.stdin.on('data', chunk => data += chunk);
process.stdin.on('end', () => {
  const input = JSON.parse(data);
  if (input.tool_name === 'Bash' && /rm\s+-rf\s+\//.test(input.tool_input.command)) {
    console.error('[Hook] BLOCKED: Dangerous rm command detected');
    process.exit(2);               // Blocks execution in PreToolUse
  }
  console.log(data);               // Must output original payload
});

Register the hook in hooks.json:

{
  "matcher": "Bash",
  "hooks": [
    {
      "type": "command",
      "command": "node danger-guard.js",
      "description": "Block destructive rm commands"
    }
  ]
}

Practical Hook Examples

Blocking Large File Creation

Prevent creation of monolithic modules exceeding 800 lines:

{
  "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 lines=(i.tool_input.content||'').split('\\n').length;if(lines>800){console.error('[Hook] BLOCKED: File exceeds 800 lines');process.exit(2)}console.log(d)})\""
  }],
  "description": "Block creation of files larger than 800 lines"
}

Enforcing Test File Creation

Require .test.ts files alongside new source files to encourage TDD:

{
  "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 for '+p);console.error('[Hook] Expected: '+testPath)}}console.log(d)})\""
  }],
  "description": "Remind to create tests when adding new source files"
}

Preventing Git Amend

Block git commit --amend to enforce linear history:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);if(/git\\s+commit/.test(i.tool_input.command)&&/--amend/.test(i.tool_input.command)){console.error('[Hook] BLOCKED: --amend is disallowed');process.exit(2)}console.log(d)})\"",
            "description": "Disallow git commit --amend"
          }
        ]
      }
    ]
  }
}

Summary

  • Hook phases: Configure automation during PreToolUse (blocking), PostToolUse (validation), Stop (batching), or Session lifecycle events
  • Installation: Use install.sh or install.ps1 with --modules hooks-runtime to properly resolve paths in ~/.claude/hooks/hooks.json
  • Configuration: Define matchers, commands, and descriptions in hooks/hooks.json following the Claude Code settings schema
  • Customization: Override behavior via ~/.claude/settings.json, environment variables (ECC_HOOK_PROFILE, ECC_DISABLED_HOOKS), or custom Node.js scripts
  • Execution model: Commands read JSON from stdin and must echo it to stdout; exit code 2 blocks execution in PreToolUse hooks
  • Observability: Hook output appears in Claude's console prefixed with [Hook]; async logs write to ~/.claude/logs

Frequently Asked Questions

How do I install ECC hooks on a new machine?

Run the platform-specific installer from the ECC repository root. On Linux or macOS, execute bash ./install.sh --target claude --modules hooks-runtime. On Windows, use pwsh -File .\install.ps1 --target claude --modules hooks-runtime. The installer rewrites path references in hooks/hooks.json and places the configuration in ~/.claude/hooks/.

Can I block dangerous commands automatically?

Yes. Create a PreToolUse hook with a matcher targeting Bash tools. In your Node.js script, inspect input.tool_input.command for dangerous patterns (like rm -rf /). Write an error message to stderr and exit with code 2 to block execution, or code 0 to allow the command with a warning.

How do I temporarily disable specific hooks without editing JSON files?

Set the ECC_DISABLED_HOOKS environment variable to a comma-separated list of hook IDs. For example, export ECC_DISABLED_HOOKS="pre:bash:tmux-reminder,post:edit:typecheck" disables those specific checks while preserving the rest of your configuration. You can also switch entire profiles using ECC_HOOK_PROFILE=minimal.

Where are hook execution logs stored?

Hook output appears inline in Claude's console prefixed with [Hook]. Asynchronous hooks (like post:bash:dispatcher) write their logs to ~/.claude/logs/. The post:observe:continuous-learning hook specifically manages continuous learning observations and audit trails for background operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →