mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-02-15 19:03:22 +08:00
* fix: Windows compatibility for hook scripts
- post-edit-format.js: add `shell: process.platform === 'win32'` to
execFileSync options so npx.cmd is resolved via cmd.exe on Windows
- post-edit-typecheck.js: same fix for tsc invocation via npx
- hooks.json: skip tmux-dependent hooks on Windows where tmux is
unavailable (dev-server blocker and long-running command reminder)
On Windows, execFileSync('npx', ...) without shell:true fails with
ENOENT because Node.js cannot directly execute .cmd files. These
hooks silently fail on all Windows installations.
The tmux hooks unconditionally block dev server commands (exit 2) or
warn about tmux on Windows where tmux is not available.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: parse Claude Code JSONL transcript format correctly
The session-end hook expected user messages at entry.content, but
Claude Code's actual JSONL format nests them at entry.message.content.
This caused all session files to be blank templates (0 user messages
despite 136+ actual entries).
- Check entry.message?.content in addition to entry.content
- Extract tool_use blocks from assistant message.content arrays
Verified with Claude Code v2.1.41 JSONL transcripts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: ddungan <sckim@mococo.co.kr>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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
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) |
| Doc file blocker | Write |
Blocks creation of random .md/.txt files (allows README, CLAUDE, CONTRIBUTING) |
2 (blocks) |
| 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) |
| 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 |
| Pre-compact | PreCompact |
Saves state before context compaction |
| Console.log audit | Stop |
Checks all modified files for console.log after each response |
| Session end | SessionEnd |
Persists session state for next session |
| Pattern extraction | SessionEnd |
Evaluates session for extractable patterns (continuous learning) |
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"
}
]
}
}
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
All hooks in this plugin use Node.js (node -e or node script.js) for maximum compatibility across Windows, macOS, and Linux. Avoid bash-specific syntax in hooks.
Related
- rules/common/hooks.md — Hook architecture guidelines
- skills/strategic-compact/ — Strategic compaction skill
- scripts/hooks/ — Hook script implementations