Skip to main content
TRW
TRWDocumentation

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

EventWhen it firesWhat TRW does
SessionStartSession opens (startup, resume, compact, clear)Loads prior learnings, recovers interrupted runs, emits tier guidance
UserPromptSubmitBefore a user prompt when the active phase changesInjects 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.
PreCompactBefore context window compactionSaves emergency checkpoint so progress survives compaction
SessionEndSession closes normallyWarns if trw_deliver was not called, preventing knowledge loss
StopUser presses Escape or session is interruptedBlocks if delivery was skipped (max 2 blocks, then advisory)

Tools

EventWhen it firesWhat TRW does
PreToolUseBefore a tool call executesGates trw_deliver until build check passes; validates PRD write scope
PostToolUseAfter Write or Edit tool completesLogs file modifications, scans for 9 OWASP security patterns, detects placeholder code replacement

Agents

EventWhen it firesWhat TRW does
SubagentStartA subagent is spawnedInjects abbreviated TRW protocol and active run context
SubagentStopA subagent completesLogs subagent completion and captures telemetry
TeammateIdleAn Agent Teams teammate has no pending tasksNotifies the lead so work can be redistributed
TaskCompletedAn Agent Teams task finishesBlocks 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.

ScriptEventPurpose
session-start.shSessionStartLoads learnings, checks for interrupted runs, emits tier-calibrated ceremony guidance
user-prompt-submit.shUserPromptSubmitPhase-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.shPreCompactSaves active run state to a recovery file before context compaction
session-end.shSessionEndWarns if events were logged but trw_deliver was never called
stop-ceremony.shStopBlocks exit if delivery was skipped (max 2 blocks, then falls through)
pre-tool-deliver-gate.shPreToolUseBlocks trw_deliver until trw_build_check has passed
post-tool-event.shPostToolUseLogs file modifications after Write/Edit tool calls
security-patterns.shPostToolUseScans 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.shPostToolUseDetects when agents replace real code with stub comments or ellipsis placeholders. Warns so the agent restores the original logic (~8ms latency)
validate-prd-write.shPreToolUseRestricts planning agents to writing only PRD and run directory files
subagent-start.shSubagentStartInjects TRW protocol context and phase guidance into spawned subagents
subagent-stop.shSubagentStopCaptures subagent completion telemetry and logs results
task-completed.shTaskCompletedQuality gate for Agent Teams: blocks if build check or checkpoints are missing
teammate-idle.shTeammateIdleNotifies 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.

.claude/settings.json (excerpt)
{
  "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.

adding a custom PostToolUse hook
// 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.

CheckPatternRisk
SEC-001eval() / exec()Arbitrary code execution
SEC-002os.system() / shell=TrueCommand injection
SEC-003pickle.loads()Deserialization attack
SEC-004SQL in f-stringsSQL injection
SEC-005Hardcoded credentialsCredential exposure
SEC-006Path traversal (../)Directory traversal
SEC-007innerHTML / dangerouslySetInnerHTMLCross-site scripting (XSS)
SEC-008Backticks in f-stringsTemplate injection
SEC-009random.random() near security contextPredictable 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.

suppression rules
ConditionHook fires?
Phase unchanged since last emissionNo — suppressed
Phase changedYes — fires, updates cache
Phase is none (no active run)Yes — always fires (session_start reminder)
Phase is doneNo — 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.

injection pipeline
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.

HookEventLatencyFrequency
security-patterns.shPostToolUse~10msEvery Edit/Write
check-comment-replacement.shPostToolUse~8msEvery Edit/Write
post-tool-event.shPostToolUse~75msEvery Edit/Write
session-start.shSessionStart~23msOnce per session
user-prompt-submit.shUserPromptSubmit~71msOn phase change only (3–5× per session)
pre-compact.shPreCompact~15msOn compaction only
stop-ceremony.shStop~50msOn 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.

Next Steps