* feat: add Plan Canvas - browser annotate-and-approve review for plan artifacts - scripts/plan-canvas.js CLI (open/await/end/stop/server; bin ecc-plan-canvas) - loopback server + ECC-styled chrome + annotation SDK + zero-dep markdown renderer - Approve/Request-changes verdicts wired to the /plan confirmation gate - plan-canvas skill, /plan-canvas command, SessionStart hook surfacing open reviews - shared scripts/lib/loopback-guard.js extracted from control-pane (API re-exported) - 121 new tests incl. full-workflow E2E; registered in manifests, catalog, registry Inspired by lavish-axi (https://github.com/kunchenguid/lavish-axi) by @kunchenguid; original ECC-native implementation, not a port. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(plan-canvas): invoke via ecc-plan-canvas bin so the skill works from any project Skill/command referenced a cwd-relative `node scripts/plan-canvas.js`, unusable outside the ECC root. Switch to the ecc-plan-canvas bin (and $CLAUDE_PLUGIN_ROOT fallback) and align CLI next_step hints so an agent can run it as a skill in any repo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(plan-canvas): render Mermaid diagrams + ship Codex cross-harness surface - markdown renderer emits <pre class="mermaid"> for ```mermaid blocks (source entity-escaped so the browser decodes it for the renderer while blocking injection) - artifact template loads a pinned Mermaid build only when a diagram is present, themed to ECC dark, securityLevel strict, graceful offline fallback to source (ECC_PLAN_CANVAS_MERMAID_URL overrides for a local mirror) - skill teaches Mermaid-for-diagrams and states the CLI+JSON loop is harness-agnostic - add .agents/skills/plan-canvas (Codex) with agents/openai.yaml interface manifest - register in install-modules workflow-quality paths; docs updated Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(plan-canvas): add demo screenshot Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): sync yarn.lock with new bin; add contributor checklist - yarn.lock records the ecc-plan-canvas bin so Yarn hardened-mode install no longer wants to modify the lockfile on public PRs - PR template + CONTRIBUTING gain a pre-push checklist covering the lockfile trap and the full skill/command/CLI registration surfaces Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Haley Chen <2022hachen@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
10 KiB
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
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.
Installing These Hooks Manually
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.
Use the installer instead so hook commands are rewritten against your actual Claude root:
bash ./install.sh --target claude --modules hooks-runtime
pwsh -File .\install.ps1 --target claude --modules hooks-runtime
That installs resolved hooks to ~/.claude/hooks/hooks.json. On Windows, the Claude config root is %USERPROFILE%\\.claude.
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) |
| 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) |
| 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) |
| Strategic compact | Edit|Write |
Suggests manual /compact at logical intervals (every ~50 tool calls) |
0 (warns) |
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) |
| Quality gate | Edit|Write|MultiEdit |
Runs fast quality checks after edits |
| Design quality check | Edit|Write|MultiEdit |
Warns when frontend edits drift toward generic template-looking UI |
| 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 |
| Plan Canvas sessions | SessionStart |
Surfaces open Plan Canvas browser reviews so a fresh session can resume the loop |
| Pre-compact | PreCompact |
Saves state before context compaction |
| Console.log audit | Stop |
Checks all modified files for console.log after each response |
| 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 |
| Desktop notify | Stop |
Sends macOS desktop notification with task summary (standard+) |
| Session end marker | SessionEnd |
Lifecycle marker and cleanup log |
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:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [],
"description": "Override: allow all .md file creation"
}
]
}
}
Runtime Hook Controls (Recommended)
Use environment variables to control hook behavior without editing hooks.json:
# 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"
# Disable only GateGuard during setup or recovery
export ECC_GATEGUARD=off
# 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
# Keep context/scope/loop warnings but suppress API-rate cost estimates
export ECC_CONTEXT_MONITOR_COST_WARNINGS=off
Windows PowerShell:
[Environment]::SetEnvironmentVariable('ECC_CONTEXT_MONITOR_COST_WARNINGS', 'off', 'User')
Profiles:
minimal— keep essential lifecycle and safety hooks only.standard— default; balanced quality + safety checks.strict— enables additional reminders and stricter guardrails.
Writing Your Own Hook
Hooks are shell commands that receive tool input as JSON on stdin and must output JSON on stdout.
Basic structure:
// 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
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):
{
"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
{
"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 files 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 alongside new source files
{
"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
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.
Related
- rules/common/hooks.md — Hook architecture guidelines
- skills/strategic-compact/ — Strategic compaction skill
- scripts/hooks/ — Hook script implementations