[Bug] Stop hooks can hang indefinitely on large JSON block responses since 2.1.78

Resolved 💬 7 comments Opened Mar 21, 2026 by Cupnfish Closed May 17, 2026

Summary

Claude Code appears to regress in 2.1.78+ when a plugin Stop hook returns a large JSON payload on stdout.

In our case, a plugin stop hook returns {"decision":"block", ...} with a large reason body containing the next-round prompt / review content. This worked in 2.1.77, but from 2.1.78 onward Claude Code can hang indefinitely at:

running stop hooks... 1/2

Regression window

  • Last known good: 2.1.77
  • First known bad: 2.1.78
  • Still reproducible in: 2.1.81

Platforms

Observed on:

  • Windows
  • macOS

We also reproduced the same blocking mechanism on Linux/WSL with a synthetic fixture, although the size threshold is higher there.

What we observe in real runs

When the bug happens:

  • the plugin has already produced the review artifact (for example round-0-review-result.md)
  • in some cases it has already also produced the next prompt and advanced loop state
  • Claude Code stays stuck on running stop hooks... 1/2
  • the UI does not return control to the session

This makes it look like the hook is still "working", even though the business logic has already completed.

Minimal reproduction pattern

The issue is not specific to one plugin implementation. The minimal triggering pattern is:

  1. A Stop hook writes a valid JSON block response to stdout
  2. The JSON payload is large enough (tens of KiB is enough on some platforms)
  3. Claude Code does not return from the stop phase

Shape of the response:

{
  "decision": "block",
  "reason": "<large payload>",
  "systemMessage": "..."
}

Controlled reproduction results

We built controlled A/B tests around the same stop-hook logic:

  • Variant A: parent process does not continuously read hook stdout
  • Variant B: parent process does continuously read hook stdout

Windows / macOS plugin reproduction

For the same hook logic:

  • Variant A hangs
  • Variant B returns quickly

Linux / WSL synthetic reproduction

With the same stop-hook structure:

  • small payloads complete normally
  • once the JSON payload crosses a threshold, Variant A hangs while Variant B completes quickly

This strongly suggests the hang is related to hook stdout consumption / backpressure rather than plugin business logic.

Why we believe this is a Claude Code regression

Two reasons:

  1. The exact same plugin workflow worked on 2.1.77
  2. The first bad version is 2.1.78, which introduced stop-hook-adjacent behavior changes in the release notes, including line-by-line response streaming and other hook/runtime handling changes

Important nuance

It is possible that plugins should avoid returning very large stop-hook payloads. That said, the current behavior is a hard hang with no explicit size-limit error or fallback behavior.

Even if Claude Code wants to impose a limit here, it would be much safer to:

  • drain hook stdout reliably, or
  • reject oversized hook responses with a clear error, rather than hanging indefinitely

Expected behavior

Claude Code should do one of the following:

  1. reliably consume stop-hook stdout even for large JSON payloads, or
  2. reject oversized stop-hook responses with a clear, deterministic error message

It should not hang indefinitely after hook business logic has already completed.

Actual behavior

Claude Code can hang indefinitely in stop-hook processing after the hook has already completed its internal work.

Notes

We separately found and fixed a plugin-side pipe issue in our own code for draining child process output from a review subprocess. That was a real bug on our side, but it does not explain this stop-hook regression window, because:

  • the same hang behavior still exists at the stop-hook stdout boundary, and
  • the last-known-good / first-known-bad split still points at 2.1.78

If helpful, I can provide the exact PowerShell and Linux repro scripts used for the A/B stdout-consumption tests.

View original on GitHub ↗

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