Bug: prompt-type Stop hooks always fail JSON validation, forcing dangerous workarounds that cause runaway background processes and unexpected billing

Resolved 💬 4 comments Opened Mar 31, 2026 by liaoandi Closed May 7, 2026

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

  1. prompt-type Stop hooks fail with JSON validation error — no supported workaround documented
  2. Claude (in-session) suggested bg_prompt_runner.sh as the fix
  3. The wrapper spawned unbounded background claude -p processes on every session end
  4. 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 using type: "prompt" should accept free-form or empty output.
  • Add a safe background hook 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 claude or claude -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.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗