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, orWriterun. 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:
matcher: A glob-style pattern selecting target tools (e.g.,Bash|Write|Edit)hooks: An array of command objects executing as Node.js one-linersdescription: 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 commandspre:write:doc-file-warning: Warns when creating non-standard markdown files (e.g., stray.txtfiles)pre:config-protection: Blocks modifications to linter or formatter configs, forcing code fixes instead of configuration changespre:governance-capture: Captures secrets or policy violations whenECC_GOVERNANCE_CAPTURE=1is enabled
PostToolUse: Automated Quality Gates
Post-execution hooks analyze completed operations:
post:bash:dispatcher: Logs PR URLs and runs async build analysispost:quality-gate: Executes fast lint and test checks on edited filespost:edit:design-quality-check: Warns when UI edits appear "template-like" or genericpost: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 plustsc --noEmiton all files edited during the current responsestop:check-console-log: Scans modified files for strayconsole.logstatementsstop:session-end: Persists session transcripts and metric aggregates
Session Lifecycle Hooks
session:start: Detects active package managers (npm, pnpm, yarn) and loads persisted contextsession: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.shorinstall.ps1with--modules hooks-runtimeto properly resolve paths in~/.claude/hooks/hooks.json - Configuration: Define matchers, commands, and descriptions in
hooks/hooks.jsonfollowing 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →