Hooks
Hooks enforce quality automatically — running shell commands at phase boundaries and session events without configuration. They load learnings at startup, save checkpoints before compaction, block delivery without passing tests, and warn when knowledge would be lost. TRW ships 13 hooks that cover the full session lifecycle.
How Hooks Work
Claude Code fires hook events at specific points in its lifecycle — when a session starts, before a tool runs, after a file is modified, and when the session ends. TRW registers shell scripts against these events in .claude/settings.json. When an event fires, Claude Code runs the matching script and reads its output.
Each hook follows a fail-open pattern: if the script errors, it exits 0 silently so your session is never broken by hook infrastructure. Hooks that enforce gates (like the delivery blocker) use exit code 2 to signal a block with an explanation message.
Info
Hooks run in the project's git root directory. They use lib-trw.sh for shared utilities like finding the active run, reading config, and logging execution telemetry.
Hook Events
Claude Code provides the hook events. TRW registers scripts against each one.
Session
| Event | When it fires | What TRW does |
|---|---|---|
| SessionStart | Session opens (startup, resume, compact, clear) | Loads prior learnings, recovers interrupted runs, emits tier guidance |
| UserPromptSubmit | Before a user prompt when the active phase changes | Injects phase-aware context reminders and up to 3 relevant learnings. Suppressed when phase has not changed — reducing emissions from 20–100 per session to 3–5. |
| PreCompact | Before context window compaction | Saves emergency checkpoint so progress survives compaction |
| SessionEnd | Session closes normally | Warns if trw_deliver was not called, preventing knowledge loss |
| Stop | User presses Escape or session is interrupted | Blocks if delivery was skipped (max 2 blocks, then advisory) |
Tools
| Event | When it fires | What TRW does |
|---|---|---|
| PreToolUse | Before a tool call executes | Gates trw_deliver until build check passes; validates PRD write scope |
| PostToolUse | After Write or Edit tool completes | Logs file modifications, scans for 9 OWASP security patterns, detects placeholder code replacement |
Agents
| Event | When it fires | What TRW does |
|---|---|---|
| SubagentStart | A subagent is spawned | Injects abbreviated TRW protocol and active run context |
| SubagentStop | A subagent completes | Logs subagent completion and captures telemetry |
| TeammateIdle | An Agent Teams teammate has no pending tasks | Notifies the lead so work can be redistributed |
| TaskCompleted | An Agent Teams task finishes | Blocks teammates who skipped build check or checkpoints |
Warning
Hooks are synchronous — they block until the script completes or times out. Each hook has a configured timeout (3–10 seconds). Keep custom hooks fast to avoid delaying the agent's workflow.
Built-in Hooks
TRW ships these 13 hook scripts. They are installed into .claude/hooks/ and registered in .claude/settings.json automatically.
| Script | Event | Purpose |
|---|---|---|
| session-start.sh | SessionStart | Loads learnings, checks for interrupted runs, emits tier-calibrated ceremony guidance |
| user-prompt-submit.sh | UserPromptSubmit | Phase-change suppression: only fires when the active phase changes. On change, injects phase-specific reminders and up to 3 contextually relevant learnings (score ≥ 0.7). Session dedup prevents re-injecting the same learning twice. 500ms timeout, fail-open. |
| pre-compact.sh | PreCompact | Saves active run state to a recovery file before context compaction |
| session-end.sh | SessionEnd | Warns if events were logged but trw_deliver was never called |
| stop-ceremony.sh | Stop | Blocks exit if delivery was skipped (max 2 blocks, then falls through) |
| pre-tool-deliver-gate.sh | PreToolUse | Blocks trw_deliver until trw_build_check has passed |
| post-tool-event.sh | PostToolUse | Logs file modifications after Write/Edit tool calls |
| security-patterns.sh | PostToolUse | Scans changed lines for 9 OWASP security patterns (eval, SQL injection, XSS, hardcoded credentials, path traversal, pickle deserialization, insecure randomness). Warns without blocking (~10ms latency) |
| check-comment-replacement.sh | PostToolUse | Detects when agents replace real code with stub comments or ellipsis placeholders. Warns so the agent restores the original logic (~8ms latency) |
| validate-prd-write.sh | PreToolUse | Restricts planning agents to writing only PRD and run directory files |
| subagent-start.sh | SubagentStart | Injects TRW protocol context and phase guidance into spawned subagents |
| subagent-stop.sh | SubagentStop | Captures subagent completion telemetry and logs results |
| task-completed.sh | TaskCompleted | Quality gate for Agent Teams: blocks if build check or checkpoints are missing |
| teammate-idle.sh | TeammateIdle | Notifies the lead agent when a teammate has no remaining tasks |
Tip
The most impactful hook is pre-tool-deliver-gate.sh. It blocks trw_deliver until trw_build_check has passed, preventing the agent from closing a session with untested code. This single gate catches the most common failure mode in AI-assisted development.
Hook Registration
Hooks are registered in .claude/settings.json under the hooks key. Each event maps to an array of matchers, and each matcher contains a shell command and timeout. The TRW installer generates this configuration automatically.
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [{
"type": "command",
"command": "sh .claude/hooks/session-start.sh",
"timeout": 5000
}]
}
],
"PreToolUse": [
{
"matcher": "mcp__trw__trw_deliver",
"hooks": [{
"type": "command",
"command": "sh .claude/hooks/pre-tool-deliver-gate.sh",
"timeout": 5000
}]
}
],
"Stop": [
{
"matcher": "",
"hooks": [{
"type": "command",
"command": "sh .claude/hooks/stop-ceremony.sh",
"timeout": 10000
}]
}
]
}
}The matcher field filters when the hook fires. An empty string matches all events of that type. A specific value like mcp__trw__trw_deliver matches only that tool call. SessionStart uses source matchers ( startup, resume, compact, clear) to fire on each session source.
Custom Hooks
You can add your own hooks alongside TRW's built-in ones. Add a new entry to the appropriate event array in .claude/settings.json. Your hook script receives the same JSON payload from stdin that TRW's hooks receive.
// In .claude/settings.json, add to the PostToolUse array:
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "sh .claude/hooks/my-bash-audit.sh",
"timeout": 3000
}]
}Custom hooks follow the same conventions: exit 0 to allow, exit 2 to block (with a message on stderr). Use the fail-open pattern — trap 'exit 0' EXIT — so errors never break the agent's session.
Real-Time Security Scanning
Every Write and Edit tool call is automatically scanned for 9 OWASP-aligned security patterns. The scan runs in ~10ms and warns the agent without blocking — giving it a chance to self-correct before the code ships.
| Check | Pattern | Risk |
|---|---|---|
| SEC-001 | eval() / exec() | Arbitrary code execution |
| SEC-002 | os.system() / shell=True | Command injection |
| SEC-003 | pickle.loads() | Deserialization attack |
| SEC-004 | SQL in f-strings | SQL injection |
| SEC-005 | Hardcoded credentials | Credential exposure |
| SEC-006 | Path traversal (../) | Directory traversal |
| SEC-007 | innerHTML / dangerouslySetInnerHTML | Cross-site scripting (XSS) |
| SEC-008 | Backticks in f-strings | Template injection |
| SEC-009 | random.random() near security context | Predictable tokens |
Phase-Change Suppression
Prior to Sprint 79, user-prompt-submit.sh fired on every prompt — injecting a reminder even when nothing had changed. In sessions with many back-and-forth prompts this produced 20–100 hook emissions, adding noise and token overhead without value.
The hook now caches the last emitted phase in .trw/run/last-hook-phase. It only fires when the active phase differs from the cached value, reducing typical emissions to 3–5 per session — one per phase transition.
| Condition | Hook fires? |
|---|---|
| Phase unchanged since last emission | No — suppressed |
| Phase changed | Yes — fires, updates cache |
Phase is none (no active run) | Yes — always fires (session_start reminder) |
Phase is done | No — always silent |
Contextual Learning Injection
When user-prompt-submit.sh fires on a phase change, it also searches prior learnings for content relevant to the user's current prompt. Matching learnings are injected directly into the prompt, giving the agent relevant institutional knowledge at the exact moment it needs it — without requiring the agent to call trw_recall manually.
1. Extract keywords from the user's prompt
2. Search .trw/learnings/ index by keyword match
3. Score each result; keep entries where score ≥ auto_recall_min_score (default 0.7)
4. Inject top 3 matching learnings (≤ auto_recall_max_tokens tokens)
5. Session dedup: skip learnings already injected this session
6. 500ms timeout guard — if search exceeds limit, inject nothing (fail-open)Info
Injection thresholds are configurable: auto_recall_min_score (default 0.7) controls relevance cutoff and auto_recall_max_tokens (default 100) caps the total token budget per injection. Both can be set in .trw/config.yaml. See the config reference for details.
Performance
Hooks are designed to be fast. Every hook uses the fail-open pattern (errors exit 0) and has strict timeouts. The total overhead per Edit/Write call is under 100ms. Session hooks fire once, not on every tool call.
| Hook | Event | Latency | Frequency |
|---|---|---|---|
| security-patterns.sh | PostToolUse | ~10ms | Every Edit/Write |
| check-comment-replacement.sh | PostToolUse | ~8ms | Every Edit/Write |
| post-tool-event.sh | PostToolUse | ~75ms | Every Edit/Write |
| session-start.sh | SessionStart | ~23ms | Once per session |
| user-prompt-submit.sh | UserPromptSubmit | ~71ms | On phase change only (3–5× per session) |
| pre-compact.sh | PreCompact | ~15ms | On compaction only |
| stop-ceremony.sh | Stop | ~50ms | On Escape only |
Troubleshooting
Hooks not firing at all
Cause: The .claude/settings.json file is missing or the hooks section was not generated by the installer.
Fix: Re-run the TRW installer or copy the hooks config from settings.json into your project.
Hook exits with "permission denied"
Cause: Shell scripts need execute permissions on Unix systems.
Fix: Run chmod +x .claude/hooks/*.sh to fix permissions.
Hook times out (no output)
Cause: Hooks have timeouts (3-10 seconds). A slow filesystem or missing jq can cause delays.
Fix: Install jq for faster JSON parsing. Check that .trw/ directory exists and is not on a network mount.