[FEATURE] Let a UserPromptSubmit hook replace the prompt, not only append to it
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
A UserPromptSubmit hook is the only thing that sees a prompt before the model
does, and it cannot change it. It can add text beside the prompt, or reject the
prompt with exit 2 and erase what was typed. Nothing in between. The
documentation states it:
UserPromptSubmit: can't replace the prompt; it only injects
additionalContext alongside it
That makes a whole class of hook unbuildable: anything that takes the user's own
text, does something to it, and sends the result instead. Hand the message to a
script or a second model and send back what it returns. Clean up a dictated
message. Expand a personal shorthand. Translate. Redact a hostname or a customer
name before the text leaves the machine.
Appending does not approximate any of them:
- Anything that compresses makes the context larger, since the model reads
both versions. The hook is worse than not running.
- Anything that redacts fails outright. The original is still there.
- Anything that rewrites a reference produces inert text. Measured on
2.1.278: a hook returning @secret/note.txt in additionalContext delivered
that text verbatim and loaded nothing, with every reading tool disallowed. The
control, the same hook returning the file's contents, answered correctly, so
the hook fired and its output arrived. Text a hook adds is not processed the
way typed text is.
Proposed Solution
Add one field. Change nothing that exists.
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"replacePrompt": "the rewritten message"
}
}
- Omitting it is today's behaviour exactly.
additionalContextis
untouched, and a hook may return both.
- The replacement is processed exactly as typed text is, so
@references
in it expand and a leading /command runs. The feature rests on this: without
it a hook can produce text that looks right and does nothing, which is what
happens today.
- The original stays in the transcript and on screen.
- Several hooks apply in configured order, each seeing the previous result,
as updatedInput already chains on PreToolUse.
Alternative Solutions
additionalContext. Adds, never replaces. Covered above.
exit 2. Rejects the prompt and erases the text. Refusing is not
correcting.
UserPromptExpansion. Fires only when a typed /command expands, and can
only block or add context.
Rewriting the text before Claude Code sees it, in a wrapper around the
terminal. Loses the session: no transcript, no working directory, none of what
makes the rewrite worth doing.
Priority
Medium - Would be very helpful
Feature Category
Configuration and settings
Use Case Example
- I dictate my prompts, and speech-to-text leaves filler and misheard words in
the text. A hook sends the raw text to a small local model with the
instruction to clean it up without changing the meaning.
- The hook returns the clean prompt in
replacePrompt. - Claude receives the clean version. The raw version stays in my transcript.
Today step 3 is impossible: the hook can only add the clean version next to the
raw one, so Claude reads both and the session is larger than if I had never
written the hook.
Additional Context
Claude Code 2.1.278.
https://code.claude.com/docs/en/hooks#json-output for the quoted limit, and
https://code.claude.com/docs/en/hooks#pretooluse-decision-control forupdatedInput, the precedent for a hook rewriting what it was handed.
Related: https://github.com/anthropics/claude-code/issues/93252