Bug: prompt-type Stop hooks always fail JSON validation, forcing dangerous workarounds that cause runaway background processes and unexpected billing
I'm reporting this as a product safety / guardrail issue. The incident originated from a documented Claude Code behavior (Stop hook JSON validation failure) that left no safe path forward, and the suggested fix — provided by Claude itself in a prior session — caused a system-level incident.
What happened
I had prompt-type Stop hooks configured for legitimate post-session automation (auto-saving memory, writing design notes, logging skill usage). These hooks began failing with:
Stop hook error: JSON validation failed
The Stop hook system validates output against a strict JSON schema (decision, reason, systemMessage). A prompt-type hook invokes Claude, whose natural language output never satisfies this schema — so validation always fails, regardless of prompt content.
With no documented safe alternative, Claude (in a subsequent session) suggested replacing the prompt-type hooks with command-type hooks backed by a wrapper script (bg_prompt_runner.sh). The wrapper would immediately return {} to pass validation, then launch claude -p in the background via &.
This workaround had no locking, no concurrency limit, no PID tracking, and no cleanup. Every session end spawned one or more background claude -p processes indefinitely.
Impact
- ~$300 in unexpected API charges from background model calls with no user-initiated sessions
- Terminal became extremely slow and unresponsive after each turn
- macOS showed sustained memory pressure warnings
- Machine became unstable and eventually crashed
Observed during investigation (MacBook Pro M3 Pro):
- Load average peaked above 1000
- 600+ runnable processes at one point
- 100+ zombie processes
- Heavy swap and memory compression activity
Root cause chain
prompt-type Stop hooks fail with JSON validation error — no supported workaround documented- Claude (in-session) suggested
bg_prompt_runner.shas the fix - The wrapper spawned unbounded background
claude -pprocesses on every session end - Processes accumulated over multiple sessions, escalating into system instability and unexpected billing
The concern is not just that a dangerous configuration is possible, but that the current hook model created a failure path where:
- The correct/safe approach was broken (JSON validation)
- The suggested fix was dangerous by design
- There were no warnings, rate limits, or safeguards to prevent escalation
Suggestions
- Fix
prompt-type Stop hooks: don't require JSON output for hooks that run side-effect tasks. Stop hooks usingtype: "prompt"should accept free-form or empty output. - Add a safe
backgroundhook type: a fire-and-forget hook with proper process lifecycle (PID tracking, timeout, concurrency limit, cleanup). - Warn against recursive model spawning: add lint-style or runtime warnings when a hook command contains
claudeorclaude -p, especially in Stop hooks. - Add concurrency safeguards on Stop hooks specifically: cap the number of concurrent Stop hook processes per session.
- Document the limitation clearly: if
prompt-type Stop hooks cannot safely produce free-form output, document this explicitly so users don't reach for workarounds.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗