Skip to main content
TRW
TRWHooks

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 13 core hooks and translates them into each client's supported surfaces where possible.

Scope note

The examples below use Claude Code hook files because they expose the underlying shell scripts most directly. Cursor, Copilot, and Gemini use translated automation surfaces, while lighter profiles like OpenCode, Codex, and Aider rely more on instructions and MCP than on shell hooks by default.

How Hooks Work

Full-surface clients expose lifecycle events 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.

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 exposes the raw event names shown here, and the other full-surface clients map equivalent automation onto the same intent.

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

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

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.

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

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

Next Steps

Next Step

Hooks wrap the lifecycle with guardrails. Troubleshooting and configuration help when you need to tune or debug that behavior.