Hooks
Hooks are optional, additive client adapters. They can surface guidance, record telemetry, snapshot local state, or provide early gate feedback when a client exposes the necessary event. TRW ships 17 top-level scripts, but no client registers every script.
How hooks work
Clients expose different lifecycle events, payloads, trust controls, and blocking semantics. TRW installs only the adapters supported by the selected client profile; the behavior is intentionally not identical everywhere.
Most policy adapters fail open on infrastructure errors. A few adapters intentionally block on a policy result, and Cursor CLI marks its before-shell and before-MCP security gates as fail closed. Treat the client row below as the source of blocking posture, not a universal exit-code rule.
Client support is not uniform
TRW maps only the events each client actually exposes. A missing hook does not remove the protocol: instruction files and MCP tool middleware remain available across clients.
Claude CodeInspect
- Project surface
.claude/settings.json- Default posture
- Broad lifecycle set. Delivery and Stop adapters can intentionally block.
Cursor IDEInspect
- Project surface
.cursor/hooks.json- Default posture
- Eight native IDE events. Pre-tool guidance remains non-blocking.
Cursor CLIInspect
- Project surface
.cursor/hooks.json- Default posture
- Five CLI events. Before-shell and before-MCP gates fail closed.
Codex CLIInspect
- Project surface
.codex/hooks.json- Default posture
- Five hooks, opt-in through [features].hooks and subject to manual trust review.
GitHub Copilot CLIInspect
- Project surface
.github/hooks/hooks.json- Default posture
- Five events routed through a client-specific payload adapter.
Antigravity CLIInspect
- Project surface
.antigravitycli/hooks.json- Default posture
- Supported PreToolUse before-edit telemetry from the bundled distill channel.
OpenCodeInspect
- Project surface
Instruction files + MCP- Default posture
- No project hook set by default. Tool middleware carries the protocol.
| Client | Project surface | Default posture |
|---|---|---|
| Claude Code | .claude/settings.json | Broad lifecycle set. Delivery and Stop adapters can intentionally block. |
| Cursor IDE | .cursor/hooks.json | Eight native IDE events. Pre-tool guidance remains non-blocking. |
| Cursor CLI | .cursor/hooks.json | Five CLI events. Before-shell and before-MCP gates fail closed. |
| Codex CLI | .codex/hooks.json | Five hooks, opt-in through [features].hooks and subject to manual trust review. |
| GitHub Copilot CLI | .github/hooks/hooks.json | Five events routed through a client-specific payload adapter. |
| Antigravity CLI | .antigravitycli/hooks.json | Supported PreToolUse before-edit telemetry from the bundled distill channel. |
| OpenCode | Instruction files + MCP | No project hook set by default. Tool middleware carries the protocol. |
Hook events
These are the behaviors registered by the bundled Claude Code template. Other clients use different names and smaller event sets; see the support matrix above.
Session
SessionStartInspect
- When it fires
- Claude session opens, resumes, clears, or returns from compaction
- What TRW does
- Emits protocol and recovery guidance. It asks the agent to call trw_session_start; it does not load memory itself.
UserPromptSubmitInspect
- When it fires
- Before a prompt when the inferred phase changes
- What TRW does
- Emits phase guidance and can inject bounded, keyword-matched local learnings.
PreCompact / PostCompactInspect
- When it fires
- Around context compaction
- What TRW does
- Snapshots local run state, then presents recovery guidance from that snapshot.
InstructionsLoadedInspect
- When it fires
- An instruction file is loaded
- What TRW does
- Records an observability event; it does not enforce policy.
SessionEndInspect
- When it fires
- A Claude session closes normally
- What TRW does
- Warns when delivery or reflection evidence is missing; never blocks.
StopInspect
- When it fires
- Claude attempts to stop
- What TRW does
- Can issue up to two delivery reminders, then allows exit.
| Event | When it fires | What TRW does |
|---|---|---|
SessionStart | Claude session opens, resumes, clears, or returns from compaction | Emits protocol and recovery guidance. It asks the agent to call trw_session_start; it does not load memory itself. |
UserPromptSubmit | Before a prompt when the inferred phase changes | Emits phase guidance and can inject bounded, keyword-matched local learnings. |
PreCompact / PostCompact | Around context compaction | Snapshots local run state, then presents recovery guidance from that snapshot. |
InstructionsLoaded | An instruction file is loaded | Records an observability event; it does not enforce policy. |
SessionEnd | A Claude session closes normally | Warns when delivery or reflection evidence is missing; never blocks. |
Stop | Claude attempts to stop | Can issue up to two delivery reminders, then allows exit. |
Tools and subagents
PreToolUseInspect
- When it fires
- Before trw_deliver, and before Write, Edit, or MultiEdit
- What TRW does
- Provides an early build-evidence check before delivery, and checks an edit against the run’s recorded intent. The trw_deliver tool middleware remains authoritative; the intent check is advisory and tamper-evident, not an enforcement boundary.
PostToolUseInspect
- When it fires
- After Claude Write, Edit, or MultiEdit calls
- What TRW does
- Records file-modification telemetry for the active run, and records what the edit actually changed against that same intent.
SubagentStart / SubagentStopInspect
- When it fires
- A Claude subagent starts or finishes
- What TRW does
- Adds abbreviated context on start and records completion telemetry on stop.
| Event | When it fires | What TRW does |
|---|---|---|
PreToolUse | Before trw_deliver, and before Write, Edit, or MultiEdit | Provides an early build-evidence check before delivery, and checks an edit against the run’s recorded intent. The trw_deliver tool middleware remains authoritative; the intent check is advisory and tamper-evident, not an enforcement boundary. |
PostToolUse | After Claude Write, Edit, or MultiEdit calls | Records file-modification telemetry for the active run, and records what the edit actually changed against that same intent. |
SubagentStart / SubagentStop | A Claude subagent starts or finishes | Adds abbreviated context on start and records completion telemetry on stop. |
Registered Claude Code set
The package ships 17 top-level hook scripts, including compatibility scripts that are not active by default. The bundled Claude template registers the subset below through .claude/settings.json. Presence in .claude/hooks/ alone does not mean a script will run.
session-start.shInspect
- Event
SessionStart- Purpose
- Emits startup, resume, clear, and compaction guidance
user-prompt-submit.shInspect
- Event
UserPromptSubmit- Purpose
- Emits phase-aware guidance and bounded local-learning candidates
pre-compact.shInspect
- Event
PreCompact- Purpose
- Writes a local pre-compaction state snapshot
post-compact.shInspect
- Event
PostCompact- Purpose
- Presents recovery guidance from the snapshot
instructions-loaded.shInspect
- Event
InstructionsLoaded- Purpose
- Records which instruction surface loaded
session-end.shInspect
- Event
SessionEnd- Purpose
- Advises when reflection or delivery evidence is missing
stop-ceremony.shInspect
- Event
Stop- Purpose
- Issues bounded delivery reminders before allowing exit
pre-tool-deliver-gate.shInspect
- Event
PreToolUse- Purpose
- Provides early feedback before trw_deliver reaches its authoritative gate
pre-tool-intent-guard.shInspect
- Event
PreToolUse- Purpose
- Checks a Write, Edit, or MultiEdit against the run’s recorded intent. Advisory and tamper-evident, not an enforcement boundary
post-tool-event.shInspect
- Event
PostToolUse- Purpose
- Records Write and Edit effects
post-tool-intent-check.shInspect
- Event
PostToolUse- Purpose
- Records what a Write, Edit, or MultiEdit actually changed against that same intent
subagent-start.shInspect
- Event
SubagentStart- Purpose
- Emits abbreviated TRW context for a spawned subagent
subagent-stop.shInspect
- Event
SubagentStop- Purpose
- Records subagent completion telemetry
| Script | Event | Purpose |
|---|---|---|
session-start.sh | SessionStart | Emits startup, resume, clear, and compaction guidance |
user-prompt-submit.sh | UserPromptSubmit | Emits phase-aware guidance and bounded local-learning candidates |
pre-compact.sh | PreCompact | Writes a local pre-compaction state snapshot |
post-compact.sh | PostCompact | Presents recovery guidance from the snapshot |
instructions-loaded.sh | InstructionsLoaded | Records which instruction surface loaded |
session-end.sh | SessionEnd | Advises when reflection or delivery evidence is missing |
stop-ceremony.sh | Stop | Issues bounded delivery reminders before allowing exit |
pre-tool-deliver-gate.sh | PreToolUse | Provides early feedback before trw_deliver reaches its authoritative gate |
pre-tool-intent-guard.sh | PreToolUse | Checks a Write, Edit, or MultiEdit against the run’s recorded intent. Advisory and tamper-evident, not an enforcement boundary |
post-tool-event.sh | PostToolUse | Records Write and Edit effects |
post-tool-intent-check.sh | PostToolUse | Records what a Write, Edit, or MultiEdit actually changed against that same intent |
subagent-start.sh | SubagentStart | Emits abbreviated TRW context for a spawned subagent |
subagent-stop.sh | SubagentStop | Records subagent completion telemetry |
Hook registration
This excerpt uses the same project-root-safe command path as the bundled Claude template. Each client has its own schema; do not copy this JSON into Cursor, Codex, Copilot, or Antigravity configuration.
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [{
"type": "command",
"command": "sh \"$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh\"",
"timeout": 5000
}]
}
],
"PreToolUse": [
{
"matcher": "mcp__trw__trw_deliver",
"hooks": [{
"type": "command",
"command": "sh \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre-tool-deliver-gate.sh\"",
"timeout": 5000
}]
}
],
"Stop": [
{
"matcher": "",
"hooks": [{
"type": "command",
"command": "sh \"$CLAUDE_PROJECT_DIR/.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. The full template registers separate SessionStart entries for startup, resume, compact, and clear. The abbreviated example shows only startup.
Custom hooks
You can add your own hooks alongside TRW's registered set. This non-blocking Claude Code example is intentionally client-specific; use your client's schema elsewhere.
// In .claude/settings.json, add to the PostToolUse array:
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "sh \"$CLAUDE_PROJECT_DIR/.claude/hooks/my-bash-audit.sh\"",
"timeout": 3000
}]
}A blocking hook needs to distinguish an intentional policy denial from an infrastructure failure. An unconditional trap 'exit 0' EXIT would erase an intentional exit 2. The bundled shell gates use an explicit flag instead:
#!/bin/sh
set -e
intentional_exit=0
trap '[ "$intentional_exit" = "1" ] || exit 0' EXIT
if policy_is_unmet; then
echo "BLOCKED: explain the required action" >&2
intentional_exit=1
exit 2
fi
exit 0Phase-change suppression
user-prompt-submit.sh avoids repeating phase guidance when the inferred phase has not changed. This keeps repeated prompts in one phase quiet.
The hook caches the last emitted phase in .trw/context/last_ups_phase. It emits again when the phase changes, when there is no active run, or after session-start logic clears the cache.
- Condition
- Phase unchanged since last emission
- Hook fires?
No - suppressed
- Condition
- Phase changed
- Hook fires?
Yes - emits and updates cache
- Condition
- Phase is
none(no active run) - Hook fires?
Yes - session-start reminder
- Condition
- Phase is
done - Hook fires?
No - silent
| Condition | Hook fires? |
|---|---|
| Phase unchanged since last emission | No - suppressed |
| Phase changed | Yes - emits and updates cache |
Phase is none (no active run) | Yes - session-start reminder |
Phase is done | No - silent |
Contextual learning injection
In clients that register user-prompt-submit.sh, a phase-change event can scan recent local learning entries and add a bounded set of keyword matches to hook output. This lightweight adapter is separate from the ranked trw_recall tool and should not be described as equivalent retrieval.
1. Extract bounded keywords from the user's prompt
2. Scan recent files in .trw/learnings/entries by keyword match
3. Score each result; keep entries where score ≥ auto_recall_min_score (default 0.7)
4. Emit up to auto_recall_max_results matches within the configured token cap
5. Session dedup: skip learnings already injected this session
6. 500ms timeout guard - if search exceeds limit, inject nothing (fail-open)Troubleshooting
Expected hook does not fire
Cause: The selected client may expose a smaller event set, require opt-in, or require trust approval.
Fix: Check the client row above, then inspect its generated config instead of assuming the Claude layout.
Hook script is present but inactive
Cause: Bundled compatibility scripts are not necessarily registered by the selected client.
Fix: Confirm that the client config contains an event entry pointing to that script.
A blocking adapter allows an operation after an error
Cause: Most policy adapters intentionally fail open on infrastructure errors.
Fix: Inspect the MCP tool result: the server-side tool or middleware gate is the authoritative decision.