[BUG] UserPromptSubmit hook registered and valid but never invoked on real prompt submission (Git Bash CLI, Windows, v2.1.261) — possible regression of #17277
Title
UserPromptSubmit hook registered and valid, but never invoked on real interactive prompt submission (Git Bash CLI, Windows)
Environment
- Claude Code version: v2.1.261
- OS: Windows 10, running Claude Code inside Git Bash (MINGW64)
- Model: Sonnet 5, medium effort
- Permission mode:
auto - Project: local project with
.claude/settings.jsondefining aUserPromptSubmithook and aPreToolUsehook (matcher:Bash)
Summary
A UserPromptSubmit hook configured in the project's .claude/settings.json is correctly listed by /hooks, and the hook script itself works perfectly when invoked manually with the exact same JSON payload Claude Code would send it — but it is never actually invoked when a real prompt is typed and submitted interactively in the CLI. The prompt reaches the model unmodified every time, across multiple fresh session restarts.
.claude/settings.json (relevant part)
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/block-secrets.js\"",
"shell": "bash"
}
]
}
]
}
}
Steps to reproduce
- Create a
UserPromptSubmithook as above, pointing to a Node.js script that:
- reads stdin JSON (
{"prompt_text": "..."}), - detects a hardcoded-looking secret in
prompt_text, - if found, writes a message to stderr and calls
process.exit(2).
- Confirm the hook is registered: run
/hooksinside the CLI session — it correctly lists theUserPromptSubmitentry pointing at the script, under "Project Settings". - Confirm the script works standalone, run directly in the same shell:
``bash`
node ".claude/hooks/block-secrets.js" <<< '{"prompt_text":"const apiKey = \"sk-ant-api03-abcdefghijklmnop1234567890\";"}'
echo "exit: $?"
2`.
Output: the expected "Blocked: detected potential credential(s)..." message, exit code
- Fully exit the
claudeprocess (/exit), start a brand-new session (claude) in the same project directory. - Type the exact same string directly into the prompt:
const apiKey = "sk-ant-api03-abcdefghijklmnop1234567890";
Expected
Per /hooks' own documented semantics for UserPromptSubmit:
Exit code 2 - block processing, erase original prompt, and show stderr to user only
The prompt should never reach the model; only the hook's stderr message should be shown to the user.
Actual
The model receives and responds to the raw prompt normally (a real assistant turn, with real token usage recorded), as if no hook were configured at all. No hook-block message is ever shown. This was verified directly by inspecting the session's .jsonl transcript in ~/.claude/projects/<project>/<session-id>.jsonl: the raw secret string appears as a plain "type":"user" message, immediately followed by a genuine "type":"assistant" message with non-zero usage.input_tokens/output_tokens — proving the model actually processed it. There is no hook-related entry anywhere near it.
This reproduced consistently across at least 3 separate fresh sessions (confirmed via distinct session transcript files), including after:
- adding
"shell": "bash"to the hook definition, - fully exiting and restarting the
claudeprocess each time, - confirming no
.claude/settings.local.jsonoverride exists, - confirming the hook appears correctly in
/hooks.
Additional notes
- A separate
PreToolUsehook (matcher:Bash), defined the same way in the samesettings.json, did fire correctly and block a matching Bash tool call — but only when observed from a different client context (Claude Code running inside the Claude desktop app's "Code" tab), not yet independently confirmed to auto-fire from this same Git Bash CLI session. permissions.denyrules in the samesettings.json(blockingRead/Bash caton.env,~/.aws/**, etc.) do work reliably and show a clear, non-silent denial message — so the settings file is being loaded and at least partially honored; it specifically appears to beUserPromptSubmithook invocation that silently doesn't happen./doctorseparately flagged "hook execution error" telemetry for aPreToolUseBash-hook run in this same project, suggesting hook execution may be broadly unreliable in this environment, not specific toUserPromptSubmit.
Impact
This defeats any credential-leak-prevention hook design relying on UserPromptSubmit to intercept typed prompts before they reach the model — the core guarantee the hook is documented to provide.
Possible regression of a previously-closed issue
This looks like a regression of #17277 ("UserPromptSubmit hook not triggering consistently"), which described the exact same symptom — the hook failing specifically on prompts submitted early in a session — and hypothesized a race condition where hook registration happens asynchronously after session init, so the first prompt(s) arrive before the handler is registered. That issue was closed in January 2026 after the reporter said it "appears to work more consistently with 2.1.3." This report reproduces the identical symptom on v2.1.261, consistently on the first prompt of freshly-restarted sessions, suggesting the underlying race condition has resurfaced. Related closed reports with similar titles: #7873, #31114.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗