Stop-hook block cap: document it, signal the override to hooks, and bless a pattern for deliberate long-running loops

Status Open
Reported on v2.1.226
Maintainer reply None cached
Activity 1 comment · opened Aug 10, 2026

Summary

Claude Code force-ends a turn after N consecutive Stop-hook blocks (N = 8 by default,
CLAUDE_CODE_STOP_HOOK_BLOCK_CAP overrides, ≤0 disables). The safeguard itself is sound —
it exists to stop buggy hooks looping forever — but three aspects make it hard to build
legitimate autonomous tooling on top of:

  1. It is undocumented. Neither the hooks documentation nor the settings/env reference

mentions the cap or the override variable (#59583 was closed but the docs still omit it
as of 2.1.226). Plugin authors discover it by decompiling the binary or by watching
their loop stop at 8 turns with no explanation they can find.

  1. The override is silent to the hook. When the cap is hit, the turn ends with a

terminal warning, but the hook that issued the block gets no feedback that its
decision: "block" was discarded (#77686). A Stop-hook-driven loop cannot distinguish
"my block was honoured and the model continued" from "my block was overridden and the
session is now idle", so its internal state silently drifts from the host's.

  1. There is no blessed pattern for deliberate long-running loops. Stop-hook blocking is

currently the only primitive for continuing autonomous work at turn end, so legitimate
autonomy frameworks and the runaway-hook safeguard are forced into the same lane. A
supported mechanism — e.g. a hook response field that requests continuation subject to
explicit opt-in limits, or a documented async re-wake contract — would separate the two.

Requests

  • Document the cap, the env var, and the stop_hook_active interaction in the hooks docs.
  • Include stop_hook_block_count (and whether the cap was hit) in Stop hook input, so a

blocking hook can see the budget it is spending and yield cleanly before force-stop.

  • State the intended pattern for long-running autonomous loops, or provide one.

Environment

Claude Code 2.1.226, Linux. Cap behaviour verified against the binary:
nge(process.env.CLAUDE_CODE_STOP_HOOK_BLOCK_CAP, 8) guarded by Ss > 0 && ea > Ss.

Impact

We ship an autonomy plugin (consciousness) whose loop is Stop-hook-driven. Until the cap
was found in the binary, sessions paused after 7–8 autonomous turns with nothing in the
docs to explain why; we now derive our yield ceiling from the env var, but that is a
dependency on undocumented internals we would gladly trade for a documented contract.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗