Hooks
Hooks and hook-like automation surfaces enforce quality automatically. They load learnings at startup, save checkpoints before compaction, block delivery without passing tests, and warn when knowledge would be lost. TRW ships 15 core hooks and translates them into each client's supported surfaces where possible.
How hooks work
Clients that expose lifecycle events do so at specific points in their workflow - when a session starts, before a tool runs, after a file is modified, and when the session ends. TRW binds its policy to those events so the same guardrails can be enforced without relying on the user to remember them.
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.
Hook events
Claude Code exposes the raw event names shown here, and the other clients with native automation surfaces map equivalent behavior onto the same intent.
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 (score >= 0.7). Suppressed when the phase has not changed, so it fires only a few times per session instead of on every prompt. |
| PreCompact | Before context window compaction | Snapshots active run state so progress survives compaction |
| PostCompact | Immediately after compaction completes | Injects recovery context so the agent resumes in place |
| InstructionsLoaded | A CLAUDE.md or rule file is loaded | Logs which instruction surface loaded for an observability audit trail |
| 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 or phase exit criteria are unmet (max 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 | Auto-logs file_modified events to the active run for progress tracking |
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 |
| HelperIdle | A delegated helper is about to go idle | Nudges the helper to keep working when tasks still remain |
| CompletionGate | A delegated helper marks work complete | Blocks helpers who skipped build check or checkpoints |
Built-in hooks
TRW ships these 15 hook scripts. In Claude Code they live in .claude/hooks/ and are registered through .claude/settings.json. Other supported clients translate the same behavior into their own configuration surfaces.
| Script | Event | Purpose |
|---|---|---|
| session-start.sh | SessionStart | Loads learnings, recovers interrupted runs, and emits value-oriented ceremony guidance. Dispatches on the startup / resume / compact / clear source |
| user-prompt-submit.sh | UserPromptSubmit | Phase-aware context injection. Only fires when the active phase changes; emits calibrated, phase-specific reminders (under ~150 tokens) and stays silent in the done phase. Fail-open: never blocks prompts. |
| pre-compact.sh | PreCompact | Snapshots active run state to .trw/context/ before context compaction |
| post-compact.sh | PostCompact | Injects recovery context immediately after compaction so the agent resumes in place |
| session-end.sh | SessionEnd | Warns if events were logged but trw_deliver was never called. Advisory only, never blocks |
| stop-ceremony.sh | Stop | Blocks exit if work was done with no reflection or delivery (max 2 blocks, then advisory) |
| phase-cycle-stop.sh | Stop | Phase exit-criteria enforcement: blocks session termination when the current phase has unmet exit criteria, up to safety-valve limits |
| pre-tool-deliver-gate.sh | PreToolUse | Blocks trw_deliver until trw_build_check has passed |
| validate-prd-write.sh | PreToolUse | Write-scope enforcement: restricts planning agents to PRD files, planning run directories, and agent memory directories |
| post-tool-event.sh | PostToolUse | Auto-logs file_modified events after Write/Edit tool completions |
| instructions-loaded.sh | InstructionsLoaded | Observability audit trail of which CLAUDE.md / rule files loaded, when, and why. Zero ceremony cost — never blocks |
| subagent-start.sh | SubagentStart | Injects the abbreviated TRW protocol, active run context, and phase-specific guidance into spawned subagents |
| subagent-stop.sh | SubagentStop | Emits structured subagent-completion telemetry to the run log. Fail-open |
| completion-gate.sh | CompletionGate | Conditional quality gate for delegated helpers: logs completion and blocks workers who skipped trw_build_check or checkpoints |
| helper-idle.sh | HelperIdle | Nudges a delegated worker that is about to go idle to keep working when tasks remain |
Hook registration
This section shows the raw Claude Code registration format. Each event maps to an array of matchers, and each matcher contains a shell command and timeout. The TRW installer generates the equivalent configuration automatically for the clients you selected.
{
"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. The example below uses Claude Code syntax, but the general pattern is the same everywhere: attach a fast script to the relevant event and let TRW keep the policy surface close to the repo.
// 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.
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)Performance
Hooks are designed to be fast. Every hook uses the fail-open pattern (errors exit 0) and has strict timeouts, so overhead per call stays low. Session hooks fire once, not on every tool call.
| Hook | Event | Latency | Frequency |
|---|---|---|---|
| post-tool-event.sh | PostToolUse | low | Every Edit/Write |
| session-start.sh | SessionStart | low | Once per session |
| user-prompt-submit.sh | UserPromptSubmit | low | On phase change only (a few times per session) |
| pre-compact.sh | PreCompact | low | On compaction only |
| stop-ceremony.sh | Stop | low | On stop/escape only |
Troubleshooting
Hooks not firing at all
Cause: The client-specific automation config is missing, or you selected a lighter profile that intentionally does not use shell hooks by default.
Fix: Re-run the installer, then inspect the generated surface for the client you selected before assuming the .claude layout should exist.
Hook exits with "permission denied"
Cause: Shell scripts need execute permissions on Unix systems.
Fix: Run chmod +x .claude/hooks/*.sh or chmod +x .cursor/hooks/*.sh for clients that use shell hook directories.
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.