UserPromptSubmit Hook in Claude Code: Validate and Transform Input
UserPromptSubmit is a Claude Code hook that runs a shell script every time you submit a prompt, before Claude sees it. Use it to prepend context, enforce policies, or block certain query patterns.
The UserPromptSubmit hook is one of the five hook types in Claude Code's event system. It fires synchronously after you press Enter but before the prompt is sent to the model, giving your script a chance to inspect, modify, or reject the input.
To configure it, add a 'UserPromptSubmit' entry under 'hooks' in your ~/.claude/settings.json. The hook receives the raw prompt text via the CLAUDE_PROMPT environment variable. Your script can exit 0 to allow the prompt through, or exit 2 to block it — exit 2 also lets you write a user-facing error message to stdout.
A common use case is automatically prepending your project's coding standards to every prompt so you don't have to repeat them. Another is blocking prompts that contain secrets like API keys or passwords before they leave your machine. You can also use it to log all prompts to a local audit file for compliance.
Because UserPromptSubmit runs on every single message, keep the script fast. Pure bash that does a regex check or a file read completes in under 5ms. Avoid network calls or heavy processes — if you need async enrichment, do it in a SessionStart hook instead and cache the result to a temp file that UserPromptSubmit can read instantly.
Examples
#!/bin/bash
# ~/.claude/hooks/user-prompt-submit.sh
# Blocks prompts that look like they contain API keys
PROMPT="$CLAUDE_PROMPT"
# Check for common secret patterns
if echo "$PROMPT" | grep -qE '(sk-[a-zA-Z0-9]{32,}|AKIA[A-Z0-9]{16}|ghp_[a-zA-Z0-9]{36})'; then
echo "Blocked: prompt appears to contain an API key or secret. Remove the secret and try again."
exit 2
fi
exit 0{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/user-prompt-submit.sh"
}
]
}
]
}
}#!/bin/bash
# Reads a cached project summary and injects it as a prefix
# (stdout is appended to the prompt when hook exits 0)
CACHE="/tmp/claude-project-context.txt"
if [ -f "$CACHE" ]; then
# Output nothing — the prepend approach needs the MCP approach instead
# For logging: just log the prompt
echo "[$(date -Iseconds)] $CLAUDE_PROMPT" >> ~/.claude/prompt-audit.log
fi
exit 0Tips
- →Exit 2 to block a prompt and show a message to the user — exit 1 causes a hard error, exit 0 allows the prompt through.
- →The CLAUDE_PROMPT env var contains the full raw prompt text including any pasted content.
- →Keep the hook under 100ms — use grep/awk rather than Python or Node for simple checks.
- →Write blocked prompt attempts to a log file for later audit without revealing the content to Claude.
- →Combine with a .claude/allowed-patterns.txt file so the policy can be updated without touching the script.
FAQ
Can UserPromptSubmit modify the prompt before Claude sees it?+
Not directly via stdout — the hook's stdout is shown to the user as a message but does not replace the prompt text. To inject context into every session, use a CLAUDE.md file with project instructions, or use the SessionStart hook to prime the conversation with a system-level message.
What is the difference between exit 1 and exit 2?+
Exit 2 is the 'block' signal: Claude Code cancels the prompt and shows any stdout your script wrote as a friendly error message. Exit 1 is treated as a hook execution failure — Claude Code logs it as an error and may still proceed depending on your 'onError' setting. Always use exit 2 for intentional blocks.
Does the hook run for slash commands like /help?+
Yes, UserPromptSubmit fires for all user input including slash commands. If you need to skip hook logic for commands, check whether CLAUDE_PROMPT starts with '/' and return exit 0 early.
Can I have multiple UserPromptSubmit hooks?+
Yes. Claude Code runs all registered hooks in order. If any hook exits 2, the prompt is blocked regardless of what later hooks would return. Order your strictest checks first.