Hook event 04 of 33

UserPromptSubmit

Runs after the user submits a prompt and before Claude processes it.

Exit code veto
Exit 2 blocks
Matchers
none
if filter
Does not apply
Docs checked
2026-08-29

Between Enter and Claude

You typed a prompt and pressed Enter. Before Claude reads a word of it, UserPromptSubmit runs. There is no matcher, because there is one kind of thing to match and that is the text itself. The payload carries permission_mode and prompt, the submitted text, alongside the fields every hook gets. The hooks reference lowers the command timeout on this event to 30 seconds. Module 09 has the other defaults.

Stdout is context here, not a log line

Here is the hidden layer that trips people up: on this event, stdout is not a log line. Exit 0 with plain text on stdout adds that text to context as a hidden system reminder: something Claude reads and you do not see in the transcript. JSON additionalContext lands the same way. Plain stdout becomes context on three events: this one, SessionStart, and UserPromptExpansion. On the rest it goes to the debug log.

Block, annotate, retitle, or suppress

The JSON answer is a top-level decision: "block" with reason, or hookSpecificOutput with additionalContext, sessionTitle, and suppressOriginalPrompt. There is no field that rewrites the prompt. A hook can stop it, add to it, name the session after it, or suppress the original. That is the full list.

Exit 2 erases the prompt

Exit 1 lets the prompt through. Valid JSON still blocks or adds context, and plain output is dropped rather than injected. Exit 2 blocks processing and erases the prompt. The JSON reason, or stderr when there is none, is shown to you and not added to context. You see why the prompt did not go through. Claude never learns it existed.

The mistake most people make first

You are developing the hook and want to see the payload, so you add print(data["prompt"]) and exit 0. The hook works. The prompt goes through, the transcript looks normal, and you move on.

Every prompt is now being echoed into Claude’s context as a hidden system reminder, once per turn, because on this event plain stdout is context. Nothing on screen shows it.

Print debugging output to stderr, which on exit 0 goes to the debug log, and keep stdout for the answer.