v0.7.0 — token-burn: Cache-friendly hook ordering — put volatile lines at tail, not head
Problem
SessionStart and UserPromptSubmit hooks inject context above stable system content (CLAUDE.md, skill bodies, MEMORY.md). Several hook outputs include volatile fields that change per turn, busting CC's prompt cache.
Evidence (verified against live hooks/hooks.json — 291 LOC, 39 hooks total)
Per-event hook counts (real, not estimated):
- SessionStart: 5 hooks
ensure-gitignore.sh,mcp-health-check.sh,deferred-tools-drift-warn.sh,write-active-workspace-sentinel.sh,session-start-prescan.sh
- UserPromptSubmit: 10 hooks
activation-routine.sh(gated: only fires whenbroin prompt or sticky-bro detected from transcript),mcp-health-check.sh,session-log-capture.sh,consultant-spawn-required.sh,roundtable-slash-detect.sh,concerns-protocol-hint.sh,push-intent-hint.sh,reonboard-intent-hint.sh,resume-intent-hint.sh,adr-required-hint.sh
- PreToolUse: 16 hooks (matched by tool name pattern)
- PostToolUse: 6 hooks
- SubagentStop: 1 hook (
swe-atomic-close.sh) - WorktreeCreate: 1 hook (
worktree-create.sh)
Volatile content in injected output:
session-start-prescan.sh(100 LOC) emits: branch, commit count, dirty count, open issue count, pending task count, file_registry warm/cold, top-level dirs, stacks detected, last 5 commits → all volatile across turns/sessionsactivation-routine.sh(123 LOC) emits:pending=#NN: <objective>banner — changes when issue state changesmcp-health-check.sh— emits status; varies across sessions
Bro-mode gating (verified in activation-routine.sh:30-46): activation-routine only emits when bro mode is active (prompt contains bro or transcript shows sticky-bro). So 10 UserPromptSubmit hooks fire, but only some emit additionalContext on a given turn.
Plan
- Restructure SessionStart hook output so stable inventory (git remotes, top-level dirs, stacks-detected) prints first; volatile counts (open issues, pending tasks, file_registry warm/cold) print last.
- Move per-turn pending-issue banner out of UserPromptSubmit injection into a dedicated bro skill that bro reads on-demand. Avoids every-turn injection.
- Audit all 39 hooks for volatile-above-stable patterns. Each hook that emits additionalContext should follow the "stable prefix, volatile suffix" rule.
- Document the cache-friendly hook contract in
docs/contributing/HOOKS.md(or create it). - Compress hook output — current
session-start-prescan.shemits multiple lines per field; could compress to one tabular line.
Acceptance criteria
- Two consecutive identical user prompts produce hook output that is byte-identical for the stable prefix ≥80%.
- A baseline session shows ≥30% better cache-hit-rate per turn after the change (measured via subjective token-cost benchmark or any cache metric CC exposes).
docs/contributing/HOOKS.mdexists with the cache-friendly contract.
Out of scope
- Removing hooks entirely.
- Restructuring CLAUDE.md cache layout (covered by #2918).
Note on source
Previous description claimed "every-turn" UserPromptSubmit injection. Verified actually 10 hooks fire per turn but activation-routine.sh (the biggest banner producer) is gated to bro-mode only. Other hooks (mcp-health-check.sh, session-log-capture.sh, *-intent-hint.sh) fire unconditionally and contribute smaller amounts. Real hook count verified at 39 across 6 event types.