-
Notifications
You must be signed in to change notification settings - Fork 2
Hooks System
How Claude Code hooks enforce the PDCA loop at the mechanical level — the fourth and fifth layers of the defense system.
Text instructions in AGENTS.md say "must read memory before coding." But in long sessions, under heavy tool use, AI models can skip text instructions. Hooks are mechanical constraints — they run as shell commands at specific lifecycle events, and can block the session with non-zero exit codes.
graph TD
START[Session Start] --> SSH[SessionStart Hook]
SSH --> PROMPT[User enters prompt]
PROMPT --> UPSH[UserPromptSubmit Hook]
UPSH --> TOOL[AI calls a tool]
TOOL --> PTUH[PreToolUse Hook]
PTUH --> RESP[AI generates response]
RESP --> STOP[Session ends]
STOP --> STOPH[Stop Hook]
STOPH -->|exit 0| END[Session ends normally]
STOPH -->|exit 2| WAKE[Wake AI: complete Act phase]
WAKE --> RESP
Trigger: Claude Code session starts.
Purpose: Verify the project environment is ready and remind about PDCA.
Actions:
- Check that
CLAUDE.mdandAGENTS.mdexist - Verify
.github/agent/memory/directory exists - Verify
.claude/settings.local.jsonexists and has valid hooks
If missing: Log a warning. This hook doesn't block — it alerts.
Trigger: User submits a prompt.
Purpose: Detect if the prompt is a coding task and remind about Plan phase.
Actions:
- Check if the prompt matches a coding task pattern (feat, fix, refactor, etc.)
- If yes, remind about the 7-file Plan phase reading
Matcher pattern: Only fires for prompts that look like coding tasks — not for conversational questions.
Trigger: AI is about to use a tool (Write, Edit, Bash, etc.).
Purpose: Block destructive operations unless explicitly authorized.
Actions:
- Block
git pushwithout explicit user request - Block SSH connections to unknown hosts
- Block
docker composecommands that modify production - Block file deletion outside the project directory
If blocked: The tool call is denied. The AI must explain why and ask for explicit authorization.
Trigger: Claude Code session is about to end.
Purpose: Verify that the Act phase was completed — memory files were updated.
Actions:
- Check if
task-history.mdwas modified during this session - If NOT modified → exit 2 (blocks session end)
-
asyncRewake: true→ wakes the AI with a prompt to run Act
Exit codes:
| Code | Meaning |
|---|---|
| 0 | Memory updated — session can end |
| 2 | Memory NOT updated — session blocked, AI wakes to complete Act |
Hooks are configured in .claude/settings.local.json:
To avoid hooks firing on every file change (README.md, package.json, etc.), hooks are scoped to source directories:
User says: "src/ tests/"
→ Becomes: ^src/\\|^tests/
This regex pattern is embedded in the hook scripts. Only changes to files matching this pattern trigger the hook logic.
Different projects have different source layouts:
-
src/ tests/— typical monorepo -
lib/ app/— Ruby/Rails -
pkg/ internal/— Go -
src/— simple project
The user knows their layout best. The default is src/ tests/.
| File | Purpose | Commit to git? |
|---|---|---|
settings.json |
Project-wide settings | ✅ Yes |
settings.local.json |
Local overrides (hooks, personal prefs) | ❌ No (gitignored) |
If both files have hooks, settings.local.json takes precedence. If both exist, the installer warns:
⚠️ Both settings.json and settings.local.json exist.
Hooks in settings.json may conflict with settings.local.json.
Recommendation: merge hooks into settings.local.json only.
The actual hook logic is in the template files. During install, the {{SOURCE_DIR_PATTERN}} is replaced:
#!/usr/bin/env bash
# Verify ai-coding-ok environment
set -e
REQUIRED_FILES=(
"AGENTS.md"
"CLAUDE.md"
".github/agent/memory/project-memory.md"
".github/agent/memory/task-history.md"
)
for file in "${REQUIRED_FILES[@]}"; do
if [[ ! -f "$file" ]]; then
echo "⚠️ [ai-coding-ok] Missing: $file"
fi
done
echo "✅ [ai-coding-ok] Environment ready. PDCA active."#!/usr/bin/env bash
# Check if memory was updated this session
# {{SOURCE_DIR_PATTERN}} is replaced during install
SOURCE_PATTERN="{{SOURCE_DIR_PATTERN}}"
# Check if task-history.md was modified
if git diff --name-only HEAD | grep -q ".github/agent/memory/task-history.md"; then
echo "✅ [ai-coding-ok] Memory updated. Session complete."
exit 0
else
echo "⛔ [ai-coding-ok] task-history.md was NOT updated!"
echo " Complete the Act phase before ending the session."
exit 2
fi| Hook | Claude Code | Copilot | Cursor |
|---|---|---|---|
| SessionStart | ✅ | ❌ | ❌ |
| UserPromptSubmit | ✅ | ❌ | ❌ |
| PreToolUse | ✅ | ❌ | ❌ |
| Stop | ✅ | ❌ | ❌ |
Hooks are Claude Code-specific (Layer 4-5). Copilot and Cursor rely on Layers 1-3 for PDCA enforcement.
The hook checks git diff. If task-history.md was modified but not staged, the diff won't show it. Make sure the file is saved.
Adjust the matcher pattern in settings.local.json to be more specific, or temporarily disable the hook:
{
"matcher": "Bash",
"hooks": [] // Disable for this session
}Each hook runs a shell command. Keep scripts lightweight (<1 second). Avoid network calls or heavy computation.
- Five-Layer Defense — how hooks fit into the overall defense system
- Installation Verification — verify hooks are configured correctly
- Troubleshooting — common hook issues and fixes
🧠 ai-coding-ok — AI 编程的 PDCA 记忆闭环。
GitHub · Issues · MIT License
{ "hooks": { "SessionStart": [ { "matcher": "", "hooks": [{ "type": "command", "command": "bash -c '... verification script ...'" }] } ], "UserPromptSubmit": [ { "matcher": "feat|fix|refactor|feature|bug|implement|add|新功能|修复|重构", "hooks": [{ "type": "command", "command": "bash -c '... reminder script ...'" }] } ], "PreToolUse": [ { "matcher": "Bash", "hooks": [{ "type": "command", "command": "bash -c '... safety check script ...'" }] } ], "Stop": [ { "matcher": "", "hooks": [{ "type": "command", "command": "bash -c '... check if task-history.md modified ...'" }] } ] } }