Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/affaan-m/ECC/llms.txt

Use this file to discover all available pages before exploring further.

Every ECC hook is defined by a trigger event, an optional matcher (filtering which tools the hook applies to), and one or more action objects. ECC supports three action types: command for shell scripts, http for webhook-style HTTP calls, and prompt/agent for sub-agent invocations. Hooks receive tool input as JSON on stdin and must echo JSON on stdout.

Hook Input Schema

All hooks receive structured input 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
  };
}

Action Types

Runs a local shell command. This is the most common hook type in ECC — all built-in hooks use Node.js scripts for cross-platform compatibility.
{
  "type": "command",
  "command": "node",
  "args": ["scripts/hooks/quality-gate.js"],
  "timeout": 30,
  "async": false
}
Fields:
FieldTypeDescription
commandstringExecutable to run (e.g., node, bash, python3)
argsarrayArguments passed to the command
envobjectAdditional environment variables
timeoutnumberSeconds before the hook is killed (default: 10)
asyncbooleanIf true, hook runs in background and cannot block
Exit codes:
  • 0 — Success, continue execution
  • 2 — Block the tool call (PreToolUse only)
  • Any other non-zero — Error (logged, does not block)
Async hooks run in the background and cannot block tool execution. Use for slow operations like background analysis:
{
  "type": "command",
  "command": "node",
  "args": ["scripts/hooks/build-analysis.js"],
  "async": true,
  "timeout": 30
}

Built-In Hook Catalog

ECC’s hooks-runtime module ships the following hooks, grouped by event:

PreToolUse Hooks

Hook IDMatcherBehaviorBlocking
pre:bash:dev-server-blockerBashBlocks npm run dev etc. outside tmux — ensures log accessYes (exit 2)
pre:bash:tmux-reminderBashSuggests tmux for long-running commands (npm test, cargo build, docker)No (warn)
pre:bash:git-push-reminderBashReminds to review changes before git pushNo (warn)
pre:bash:pre-commit-qualityBashRuns quality checks before git commit: lints staged files, validates commit message, detects secretsConditional
pre:write:doc-file-warningWriteWarns about non-standard .md/.txt filesNo (warn)
pre:edit:strategic-compactEdit|WriteSuggests /compact at logical intervals (~50 tool calls)No (warn)

PostToolUse Hooks

Hook IDMatcherWhat It Does
post:bash:pr-loggerBashLogs PR URL and review command after gh pr create
post:bash:build-analysisBashBackground analysis after build commands (async)
post:edit:quality-gateEdit|Write|MultiEditRuns fast quality checks after edits
post:edit:design-qualityEdit|Write|MultiEditWarns when frontend edits drift toward generic UI
post:edit:prettierEditAuto-formats JS/TS files with Prettier
post:edit:typecheckEditRuns tsc --noEmit after editing .ts/.tsx files
post:edit:console-log-warningEditWarns about console.log statements in edited files

Lifecycle Hooks

Hook IDEventWhat It Does
session:startSessionStartLoads previous context and detects package manager
pre:compactPreCompactSaves state before context compaction
stop:check-console-logStopAudits all modified files for console.log
stop:session-summaryStopPersists session state when transcript path is available
stop:pattern-extractionStopEvaluates session for extractable patterns (continuous learning)
stop:cost-trackerStopEmits lightweight run-cost telemetry markers
stop:desktop-notifyStopSends macOS desktop notification with task summary
session:endSessionEndLifecycle marker and cleanup log

Custom Hook Recipes

Warn about TODO comments

{
  "matcher": "Edit",
  "hooks": [{
    "type": "command",
    "command": "node",
    "args": ["-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",
    "args": ["-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)');process.exit(2)}console.log(d)})"]
  }],
  "description": "Block creation of files larger than 800 lines"
}

Auto-format Python files with ruff

{
  "matcher": "Edit",
  "hooks": [{
    "type": "command",
    "command": "node",
    "args": ["-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 alongside new source files

{
  "matcher": "Write",
  "hooks": [{
    "type": "command",
    "command": "node",
    "args": ["-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.log(d)})"]
  }],
  "description": "Remind to create tests when adding new source files"
}

Overriding Built-In Hooks

To disable a specific built-in hook without uninstalling the module, override it in ~/.claude/settings.json:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [],
        "description": "Override: allow all .md file creation"
      }
    ]
  }
}
Or use environment variables for runtime control (see Lifecycle Events for the full list of ECC_* controls).

Build docs developers (and LLMs) love