[FEATURE] Stop/PreCompact/PostCompact hooks need invisible model-context injection (parity with SessionStart/PreToolUse)
Summary
Stop, PreCompact, and PostCompact hooks have no way to inject model-only context invisible to the user. The Stop hook can only emit decision: 'block', reason: '...', where reason is rendered in the user's chat. PreCompact/PostCompact have similarly limited output. This forces plugin authors into a bad trade-off:
- Inject scaffolding (recap requests, continuation prompts, memory synthesis cues) → spam the user's chat with prompt-engineering text intended only for Claude
- Drop the feature entirely
In contrast, SessionStart, PreToolUse, PostToolUse, and UserPromptSubmit all support hookSpecificOutput.additionalContext, which is added to Claude's context invisibly.
Verified schema (CC 2.1.114, docs scraped 2026-04-19)
| Hook | hookSpecificOutput.additionalContext |
|------|----------------------------------------|
| PreToolUse | ✅ supported |
| PostToolUse | ✅ supported |
| UserPromptSubmit | ✅ supported |
| SessionStart | ✅ supported |
| Stop | ❌ rejected by validator |
| PreCompact | ❌ rejected |
| PostCompact | ❌ no event-specific output at all |
Submitting {decision: 'block', hookSpecificOutput: {hookEventName: 'Stop', additionalContext: '...'}} produces:
Stop hook error: Hook JSON output validation failed — (root): Invalid input
Why this matters
Plugin authors building on top of CC (wiz/lansweeper, swarms, custom workflows) routinely need to:
- End-of-turn recap requests: ask the model to summarize what changed without spamming the user's chat with the prompt itself
- Continuation prompts in unattended mode: re-inject task queue/story state to keep the model on track when it tries to stop prematurely
- Behavior-correction nudges: remind the model of constraints it just violated (e.g., \"you marked something pre-existing without verifying\") without surfacing the rule text to the user
- Post-compact context restoration: re-inject active story/plan after compaction destroys it
Today every one of these either (a) becomes visible noise in the user's chat, (b) requires deferring injection to UserPromptSubmit on the next turn (adds latency, breaks immediacy), or (c) is silently dropped.
Proposed fix
Add hookSpecificOutput.additionalContext: string (optional) support to:
Stop(withhookSpecificOutput.hookEventName: 'Stop')PreCompact(withhookSpecificOutput.hookEventName: 'PreCompact')PostCompact(withhookSpecificOutput.hookEventName: 'PostCompact')
Semantics identical to existing supported events: text is appended to Claude's next-turn context, not rendered in the user's transcript. The schema validator should accept this shape and the renderer should suppress display.
For Stop specifically: keep decision: 'block', reason: '...' as the visible-to-user path (unchanged). additionalContext would be the invisible-to-user path. Both can coexist as they do for PostToolUse.
Bigger picture
CC is a powerful base for plugins, but the current Stop/PreCompact/PostCompact constraints push every plugin author into the same bad workaround. Plugin authors who want to customize the workflow without sharing Anthropic's default UX assumptions need a clean way to inject model-only context across all hook events. Otherwise CC ends up looking like \"a container of constant on-screen errors\" from the user's perspective — every turn ends with a Stop hook error: ... block, every customization leaks prompt-engineering text into the chat.
The asymmetry across hook events also makes the API harder to learn (\"why does it work in SessionStart but fail in Stop?\"). Adding additionalContext uniformly removes that wart.
Workaround status
For wiz I am currently downgrading to top-level reason (visible to user) and accepting the cost, while documenting the constraint in the plugin's CLAUDE.md. Reproducible diff and tests on request.
Repro
echo '{}' | hook_script_that_emits_stop_with_additionalContext
Output:
Stop hook error: Hook JSON output validation failed — (root): Invalid input
Expected: text is appended to model context for next turn, no user-visible message.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗