UserPromptSubmit hook: decision: "block" does not erase typed prompt from model context
Summary
When a UserPromptSubmit hook returns hookSpecificOutput.decision: "block", the docs state the prompt is "erased from context" and "removed from the transcript." Empirically this does not appear to be the case — the literal text the user typed reaches the model in conversation context, and the model can quote it back in the same or subsequent turns.
This breaks the intended design of any hook that uses block to prevent sensitive text (PII, secrets, etc.) from reaching the model.
Repro
- Register a
UserPromptSubmithook (e.g. a small shell script invoking a Python module) that, for any prompt matching^foo: bar, returns a sanitized receipt that does NOT contain the user-supplied argument:
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"decision": "block",
"reason": "[foo] registered SECRET_PLACEHOLDER",
"additionalContext": "[foo] registered SECRET_PLACEHOLDER"
}
}
- Verify the hook output directly — the user-supplied argument appears NOWHERE in the JSON the hook emits.
- In an interactive Claude Code session with the hook installed, type:
foo: bar SOME_DISTINCTIVE_STRING
- Observe the model's response. The model can reference
SOME_DISTINCTIVE_STRINGin its reply — for example by paraphrasing it back, or by mapping it to the placeholder ("SOME_DISTINCTIVE_STRING→SECRET_PLACEHOLDER").
The string is not in the hook output. The only place it existed is the user's typed prompt, which decision: "block" is supposed to have erased from context.
Expected behavior
Per the hooks docs:
When decision: "block" is set, the prompt is erased from context and removed from the transcript.
The model should never see the typed prompt — only the reason string should be surfaced (to the user, not the model).
Actual behavior
The model sees the typed prompt verbatim and can quote it back on the same turn. (Subsequent-turn behavior not yet rigorously confirmed but appears similar.)
Notes
- The hook output sets both
reasonandadditionalContext. Per the docs,additionalContextis ignored whendecision: "block"is set, so this should not matter — the leak appears regardless. - Anecdotally, when the cleartext is placed in the body (lines after the trigger line) rather than as args on the trigger line, the leak is harder to reproduce. So this may be specific to the trigger / first line of a multi-line prompt, or specific to single-line prompts. Happy to dig deeper if a maintainer wants more data.
Environment
- Platform: Windows 11 Enterprise
- Shell: Git Bash + PowerShell
- Claude Code version: 2.1.123
- Model: claude-opus-4-7
Why it matters
decision: "block" is the only documented mechanism for a UserPromptSubmit hook to prevent text from reaching the model. There is no documented replacePrompt / rewrite mechanism. If block doesn't actually block, hooks designed to redact sensitive user input before it reaches the model have a silent leak.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗