2026-02-20 01:11:30 -08:00
# Hooks
Hooks are event-driven automations that fire before or after Claude Code tool executions. They enforce code quality, catch mistakes early, and automate repetitive checks.
## How Hooks Work
```
User request → Claude picks a tool → PreToolUse hook runs → Tool executes → PostToolUse hook runs
```
- **PreToolUse** hooks run before the tool executes. They can **block** (exit code 2) or **warn** (stderr without blocking).
- **PostToolUse** hooks run after the tool completes. They can analyze output but cannot block.
- **Stop** hooks run after each Claude response.
- **SessionStart/SessionEnd** hooks run at session lifecycle boundaries.
- **PreCompact** hooks run before context compaction, useful for saving state.
## Hooks in This Plugin
2026-05-11 20:15:37 -04:00
Memory persistence lifecycle definitions live in `hooks/memory-persistence/` .
The executable hook graph remains `hooks/hooks.json` ; the memory persistence directory is the stable contract for SessionStart, PreCompact, observation, activity tracking, and SessionEnd behavior.
2026-04-12 23:29:45 -07:00
## Installing These Hooks Manually
2026-04-14 20:24:21 -07:00
For Claude Code manual installs, do not paste the raw repo `hooks.json` into `~/.claude/settings.json` or copy it directly into `~/.claude/hooks/hooks.json` . The checked-in file is plugin/repo-oriented and is meant to be installed through the ECC installer or loaded as a plugin.
2026-04-12 23:29:45 -07:00
Use the installer instead so hook commands are rewritten against your actual Claude root:
```bash
2026-08-07 15:18:14 -04:00
bash ./install.sh --target claude --modules hooks-runtime --enable-hooks
2026-04-12 23:29:45 -07:00
```
```powershell
2026-08-07 15:18:14 -04:00
pwsh -File .\ install . ps1 - -target claude - -modules hooks-runtime - -enable-hooks
2026-04-12 23:29:45 -07:00
```
2026-09-07 00:53:13 +08:00
That installs the hook scripts under `~/.claude/` and registers the resolved
hook entries in `~/.claude/settings.json` . Existing user settings and hook
entries are preserved, while ECC-owned entries are tracked by stable ID for
idempotent updates and safe uninstall. On Windows, the Claude config root is
2026-09-07 01:57:04 +08:00
`%USERPROFILE%\.claude` .
2026-04-12 23:29:45 -07:00
2026-02-20 01:11:30 -08:00
### PreToolUse Hooks
| Hook | Matcher | Behavior | Exit Code |
|------|---------|----------|-----------|
| **Dev server blocker** | `Bash` | Blocks `npm run dev` etc. outside tmux — ensures log access | 2 (blocks) |
| **Tmux reminder** | `Bash` | Suggests tmux for long-running commands (npm test, cargo build, docker) | 0 (warns) |
| **Git push reminder** | `Bash` | Reminds to review changes before `git push` | 0 (warns) |
2026-03-29 00:07:18 -04:00
| **Pre-commit quality check** | `Bash` | Runs quality checks before `git commit` : lints staged files, validates commit message format when provided via `-m/--message` , detects console.log/debugger/secrets | 2 (blocks critical) / 0 (warns) |
2026-02-24 13:51:13 +05:30
| **Doc file warning** | `Write` | Warns about non-standard `.md` /`.txt` files (allows README, CLAUDE, CONTRIBUTING, CHANGELOG, LICENSE, SKILL, docs/, skills/); cross-platform path handling | 0 (warns) |
2026-02-20 01:11:30 -08:00
| **Strategic compact** | `Edit\|Write` | Suggests manual `/compact` at logical intervals (every ~50 tool calls) | 0 (warns) |
2026-04-08 15:40:26 -07:00
2026-02-20 01:11:30 -08:00
### PostToolUse Hooks
| Hook | Matcher | What It Does |
|------|---------|-------------|
| **PR logger** | `Bash` | Logs PR URL and review command after `gh pr create` |
| **Build analysis** | `Bash` | Background analysis after build commands (async, non-blocking) |
2026-03-04 14:48:06 -08:00
| **Quality gate** | `Edit\|Write\|MultiEdit` | Runs fast quality checks after edits |
2026-04-02 17:33:17 -07:00
| **Design quality check** | `Edit\|Write\|MultiEdit` | Warns when frontend edits drift toward generic template-looking UI |
2026-02-20 01:11:30 -08:00
| **Prettier format** | `Edit` | Auto-formats JS/TS files with Prettier after edits |
| **TypeScript check** | `Edit` | Runs `tsc --noEmit` after editing `.ts` /`.tsx` files |
| **console.log warning** | `Edit` | Warns about `console.log` statements in edited files |
### Lifecycle Hooks
| Hook | Event | What It Does |
|------|-------|-------------|
| **Session start** | `SessionStart` | Loads previous context and detects package manager |
2026-07-08 17:12:48 -04:00
| **Plan Canvas sessions** | `SessionStart` | Surfaces open Plan Canvas browser reviews so a fresh session can resume the loop |
2026-02-20 01:11:30 -08:00
| **Pre-compact** | `PreCompact` | Saves state before context compaction |
| **Console.log audit** | `Stop` | Checks all modified files for `console.log` after each response |
2026-03-04 14:48:06 -08:00
| **Session summary** | `Stop` | Persists session state when transcript path is available |
| **Pattern extraction** | `Stop` | Evaluates session for extractable patterns (continuous learning) |
| **Cost tracker** | `Stop` | Emits lightweight run-cost telemetry markers |
2026-03-24 10:17:42 +09:00
| **Desktop notify** | `Stop` | Sends macOS desktop notification with task summary (standard+) |
2026-03-04 14:48:06 -08:00
| **Session end marker** | `SessionEnd` | Lifecycle marker and cleanup log |
2026-02-20 01:11:30 -08:00
## Customizing Hooks
### Disabling a Hook
Remove or comment out the hook entry in `hooks.json` . If installed as a plugin, override in your `~/.claude/settings.json` :
```json
{
"hooks" : {
"PreToolUse" : [
{
"matcher" : "Write" ,
"hooks" : [],
"description" : "Override: allow all .md file creation"
}
]
}
}
```
2026-03-04 14:48:06 -08:00
### Runtime Hook Controls (Recommended)
Use environment variables to control hook behavior without editing `hooks.json` :
```bash
2026-08-06 15:39:49 -04:00
# Master switch. Explicit environment values override plugin preferences.
export ECC_HOOKS_ENABLED = true
2026-03-04 14:48:06 -08:00
# minimal | standard | strict (default: standard)
export ECC_HOOK_PROFILE = standard
# Disable specific hook IDs (comma-separated)
export ECC_DISABLED_HOOKS = "pre:bash:tmux-reminder,post:edit:typecheck"
2026-04-30 08:31:48 -04:00
2026-04-30 10:45:43 -04:00
# Disable only GateGuard during setup or recovery
export ECC_GATEGUARD = off
2026-04-30 08:31:48 -04:00
# Cap SessionStart additional context (default: 8000 chars)
export ECC_SESSION_START_MAX_CHARS = 4000
# Disable SessionStart additional context entirely
export ECC_SESSION_START_CONTEXT = off
2026-05-17 01:43:50 -04:00
# Keep context/scope/loop warnings but suppress API-rate cost estimates
export ECC_CONTEXT_MONITOR_COST_WARNINGS = off
```
Windows PowerShell:
```powershell
[ Environment ]:: SetEnvironmentVariable ( 'ECC_CONTEXT_MONITOR_COST_WARNINGS' , 'off' , 'User' )
2026-03-04 14:48:06 -08:00
```
2026-08-06 15:39:49 -04:00
Claude setup-only value:
- `off` — disables local ECC hook work through `ecc setup` ; it is not a runtime hook profile.
Runtime hook profiles:
2026-03-04 14:48:06 -08:00
- `minimal` — keep essential lifecycle and safety hooks only.
- `standard` — default; balanced quality + safety checks.
- `strict` — enables additional reminders and stricter guardrails.
2026-08-06 15:39:49 -04:00
The Claude plugin exposes the same choices as the personal `hooks_enabled` and
`hook_profile` settings. Run `ecc setup --mode claude-plugin` to install or
update the plugin and change those preferences.
2026-02-20 01:11:30 -08:00
### Writing Your Own Hook
Hooks are shell commands that receive tool input as JSON on stdin and must output JSON on stdout.
**Basic structure:**
```javascript
// 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", "Write", etc.
const toolInput = input . tool_input ; // Tool-specific parameters
const toolOutput = input . tool_output ; // Only available in PostToolUse
// Warn (non-blocking): write to stderr
console . error ( '[Hook] Warning message shown to Claude' );
// Block (PreToolUse only): exit with code 2
// process.exit(2);
// Always output the original data to stdout
console . log ( data );
});
```
**Exit codes:**
- `0` — Success (continue execution)
- `2` — Block the tool call (PreToolUse only)
- Other non-zero — Error (logged but does not block)
### Hook Input Schema
```typescript
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
};
}
```
### Async Hooks
For hooks that should not block the main flow (e.g., background analysis):
```json
{
"type" : "command" ,
"command" : "node my-slow-hook.js" ,
"async" : true ,
"timeout" : 30
}
```
Async hooks run in the background. They cannot block tool execution.
## Common Hook Recipes
### Warn about TODO comments
```json
{
"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
```json
{
"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 files with ruff
```json
{
"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 alongside new source files
```json
{
"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 Notes
2026-04-29 21:18:29 -04:00
Hook logic is implemented in Node.js scripts for cross-platform behavior on Windows, macOS, and Linux. The continuous-learning observer is exposed as a Node-mode hook and delegates to its existing `observe.sh` implementation through a profile-gated runner with Windows-safe fallback behavior.
2026-02-20 01:11:30 -08:00
## Related
- [rules/common/hooks.md ](../rules/common/hooks.md ) — Hook architecture guidelines
- [skills/strategic-compact/ ](../skills/strategic-compact/ ) — Strategic compaction skill
- [scripts/hooks/ ](../scripts/hooks/ ) — Hook script implementations