UserPromptSubmit hook: decision: "block" does not erase typed prompt from model context

Resolved 💬 2 comments Opened Apr 30, 2026 by rjskene Closed Jun 2, 2026

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

  1. Register a UserPromptSubmit hook (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"
  }
}
  1. Verify the hook output directly — the user-supplied argument appears NOWHERE in the JSON the hook emits.
  1. In an interactive Claude Code session with the hook installed, type:
foo: bar SOME_DISTINCTIVE_STRING
  1. Observe the model's response. The model can reference SOME_DISTINCTIVE_STRING in its reply — for example by paraphrasing it back, or by mapping it to the placeholder ("SOME_DISTINCTIVE_STRINGSECRET_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 reason and additionalContext. Per the docs, additionalContext is ignored when decision: "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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗