fix(claude): recover from "Prompt is too long" & "Autocompact is thrashing" when auto-compaction fails (#1841)#1842
fix(claude): recover from "Prompt is too long" & "Autocompact is thrashing" when auto-compaction fails (#1841)#1842konard wants to merge 9 commits into
Conversation
Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: #1841
…ails (#1841) Claude Code's context window fills, its built-in auto-compaction fails (compact_result: failed / compact_error: too_few_groups), and the next call returns the synthetic "Prompt is too long" (terminal_reason: blocking_limit), exiting 1 with no recovery. Root cause is upstream (anthropics/claude-code#46348). - classifyRetryableError flags "Prompt is too long" / "input is too long" with { requiresFreshSession: true, isContextLimit: true } (centralized, so every tool benefits) - new createContextLimitRecovery discards the session and restarts fresh (resuming replays the over-long transcript), auto-committing work first; capped by maxContextLimitRestarts (default 1) - wired into both the streamed-result and thrown-exception paths; thinking-block recovery now guarded with !isContextLimit so the two recoveries don't collide - verbose auto-compaction lifecycle tracing (compacting / FAILED / Prompt-too-long) - deep case study + upstream-report draft under docs/case-studies/issue-1841 - 22 new assertions in tests/test-issue-1841-context-limit-recovery.mjs
Prompt is too long?
Working session summaryAll work is complete. Here is the final summary. Issue #1841 —
|
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost: $8.449472📊 Context and tokens usage:Claude Opus 4.8: (3 sub-sessions)
Total: (43.2K new + 340.8K cache writes + 8.3M cache reads) input tokens, 75.6K output tokens, $8.397241 cost Claude Haiku 4.5:
Total: 26.1K input tokens, 1.2K output tokens, $0.052232 cost 🤖 Models used:
📎 Log file uploaded as Gist (4227KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
✅ Ready to mergeThis pull request is now ready to be merged:
Monitored by hive-mind with --auto-restart-until-mergeable flag |
This reverts commit 3d0c311.
|
As I know it is possible to configure compactification in Claude Code, can we make the threshold smaller or limit the compactification output to actually reduce the size of summary prompt? Double check all the docs of Claude Code on the topic, and check it locally. |
|
🤖 AI Work Session Started Starting automated work session at 2026-05-30T00:06:59.883Z The PR has been converted to draft mode while work is in progress. This comment marks the beginning of an AI work session. Please wait for the session to finish, and provide your feedback. |
…oups (#1841) The failing run had already lowered the auto-compaction *threshold* (CLAUDE_CODE_AUTO_COMPACT_WINDOW=150000 via --sub-session-size) yet still hit "Prompt is too long": a single assistant turn emitted 125,310 output tokens (allowed by CLAUDE_CODE_MAX_OUTPUT_TOKENS=128000), forming one un-splittable group → compact_error: too_few_groups → synthetic blocking_limit failure. Lowering the threshold cannot fix this; the only lever is bounding per-turn output. getClaudeEnv now caps CLAUDE_CODE_MAX_OUTPUT_TOKENS at floor(window * 0.45) (fraction < 0.5 guarantees >=2 groups fit), with a 32K floor. Configurable via HIVE_MIND_MAX_OUTPUT_COMPACTION_FRACTION (0 disables) and HIVE_MIND_MIN_OUTPUT_TOKENS. - config.lib.mjs: computeCompactionSafeOutputCap + knobs, applied in getClaudeEnv - claude.lib.mjs: verbose log when the per-turn cap is applied - tests: 11 new assertions (cap math + getClaudeEnv integration), 33 total - docs: case-study §4.1 'Can we configure compaction?' verified vs binary v2.1.157 (threshold clamp min 1e5 / max 1e6, capped to model window)
Great question — I dug into both the docs and the installed binary (Claude Code v2.1.157) and the answer is nuanced. Yes, we can configure it — but making the threshold smaller does not fix this failure; limiting per-turn output does. I've now implemented the latter. Full write-up: The three knobs (verified against the v2.1.157 binary)
Why lowering the threshold alone can't workThe failing run already had the threshold lowered: The fix (this is what "limit the compaction output" means in practice)
Configurable / escapable:
This is defense-in-depth: the cap prevents the failure; the existing fresh-session recovery still catches it if it ever occurs anyway. Verified locallyCommits: |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost: $8.413749📊 Context and tokens usage:Claude Opus 4.8: (2 sub-sessions)
Total: (14.7K new + 276.2K cache writes + 9.1M cache reads) input tokens, 81.9K output tokens, $8.413749 cost 🤖 Models used:
📎 Log file uploaded as Gist (3956KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
✅ Ready to mergeThis pull request is now ready to be merged:
Monitored by hive-mind with --auto-restart-until-mergeable flag |
|
Looks like we have similar issue at Also I want to clearly see what was changed in this pull request with before and after summary. And check not only for ENVs, but also CLI options on Claude Code. And we need to make sure that for all errors we do auto-commit of uncommitted changes. We need to make sure by fixing this pull request, we don't break anything existing, so the backward compatibility will be preserved, yet, we might want to make compaction a bit earlier. |
|
🤖 AI Work Session Started Starting automated work session at 2026-05-30T11:30:45.859Z The PR has been converted to draft mode while work is in progress. This comment marks the beginning of an AI work session. Please wait for the session to finish, and provide your feedback. |
…rrors (#1841) - Classify "Autocompact is thrashing" / rapid_refill_breaker as a context-limit failure (requiresFreshSession + isContextLimit), routed to the same fresh-session recovery as "Prompt is too long". Detected/traced in claude.lib.mjs. - exit-handler: run interrupt (auto-commit + push) on uncaughtException / unhandledRejection (previously cleanup-only), so all error paths preserve work. - Generalize context-limit-recovery docs to cover both failure modes. - Verify against Claude Code v2.1.158 binary: no compaction CLI flags (env-only); clamp constants cc6=1e5/hL4=1e6; breaker constants nc6=t08=3. - Update case study (before/after summary, both failure modes, version, CLI-flags finding, new env vars) and upstream-report; +6 thrashing tests (39 total).
The thrashing-detection + tracing additions pushed src/claude.lib.mjs to 1509 lines, tripping the max-lines eslint rule and check-file-line-limits.sh (limit 1500). Merge the two context-exhaustion detection blocks (same recovery) and condense the verbose comments I added; behavior unchanged. Now 1498 lines; 39/39 issue tests pass.
Working session summaryThe background CI watcher confirms it: Everything is finalized. PR #1842 is ready for review with all CI green, working tree clean, and all six feedback items from the user's comment addressed. No further action needed. This summary was automatically extracted from the AI working session output. |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost: $11.013480📊 Context and tokens usage:Claude Opus 4.8: (3 sub-sessions)
Total: (31.2K new + 341.9K cache writes + 12.3M cache reads) input tokens, 102.9K output tokens, $11.013481 cost 🤖 Models used:
📎 Log file uploaded as Gist (5749KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
✅ Ready to mergeThis pull request is now ready to be merged:
Monitored by hive-mind with --auto-restart-until-mergeable flag |
|
Double check https://gist.githubusercontent.com/konard/dfda1530492ec2033ad249dcb45efba3/raw/691382f8dd05d0a6aee404629a56f354cbfacd03/solution-draft-log-pr-1780139614800.txt is included to analysis. Re-read again #1842 (comment) We need to ensure all changes are correct, consistent, validated, tested, documented, logged and fully meet each and all discussed requirements in deepest and widest possible sense (check issue description and all comments in issue and in pull request, make sure each and every requirement listed before actually checking if they were addressed). Nothing should be defered or delayed. The scope is the entire repository, every change in one place should also be applied in all places in the codebase and docs. Ensure all CI/CD checks pass. |
Summary
Fixes #1841. A headless
solverun filled Claude Code's 200K-token context window. Claude Code's built-in auto-compaction triggered (system status: compacting) but failed (compact_result: failed,compact_error: too_few_groups— one ~125K-token turn dominated the window), so the prompt could never be reduced. The next API call returned the syntheticPrompt is too long(error: invalid_request,terminal_reason: blocking_limit) and the process exited 1 with no recovery.This PR also handles a second, closely-related dead-end in the same subsystem (requested in PR review):
Autocompact is thrashing(terminal_reason: rapid_refill_breaker) — emitted when a large file read or tool output keeps refilling the context within a few turns of each compaction, tripping Claude Code's rapid-refill breaker after 3 rapid refills in a row.Root cause for both is on the Claude Code side — a well-documented upstream limitation (anthropics/claude-code#46348 and duplicates). On hive-mind's side the failures were simply unhandled exit-1s. This PR adds prevention (a per-turn output cap so a single turn can't dominate the compaction window) + detection of both modes + fresh-session recovery + verbose compaction tracing + auto-commit on all errors.
Before / after
Prompt is too long(compactiontoo_few_groups)maxContextLimitRestartsAutocompact is thrashing(rapid_refill_breaker)terminal_reason) → same fresh-session recoveryCLAUDE_CODE_MAX_OUTPUT_TOKENSup to 128K → one un-splittable group →too_few_groupsgetClaudeEnvcaps per-turn output atfloor(window × 0.45)→ ≥2 groups always fit →too_few_groupspreventeduncaughtException/unhandledRejection)🗜️ compacting,⚠️ compaction FAILED,📏 Detected …,📏 Capped per-turn output …)150k--sub-session-sizedefault is unchanged; compacting earlier stays opt-inThe failure mechanisms
too_few_groups→Prompt is too long:rapid_refill_breaker→Autocompact is thrashing:The breaker is gated behind
tengu_auto_compact_rapid_refill_breaker; the "3 turns / 3 times" figures are hard-coded constants (nc6=3,t08=3) in the v2.1.158 binary, with no env/CLI override.Can we configure compaction to prevent this? (PR review question — verified locally)
Yes for
too_few_groups— but lowering the threshold does not fix it; bounding per-turn output does. Verified against the installed binary (Claude Code v2.1.158, strings/constants read directly):CLAUDE_CODE_AUTO_COMPACT_WINDOW(threshold) → effective window ismin(modelWindow, max(1e5, min(value, 1e6)))(clamp constantscc6=1e5,hL4=1e6). The failing run already had this at150000(via--sub-session-size) and still failed.too_few_groupsis not about when compaction triggers — it's about whether the transcript splits into ≥2 groups. One ~125K-token turn (allowed byCLAUDE_CODE_MAX_OUTPUT_TOKENS=128000) is a single un-splittable group; triggering earlier doesn't add groups.CLAUDE_CODE_MAX_OUTPUT_TOKENS— bounding the largest single turn.claude --help(v2.1.158). Checked per PR feedback ("check not only for ENVs, but also CLI options").rapid_refill_breakerthrashing is input-driven, so the output cap does not prevent it — reading large files in smaller chunks does; recovery (fresh session) is the safety net.getClaudeEnvnow capsCLAUDE_CODE_MAX_OUTPUT_TOKENSatfloor(window × 0.45)(fraction< 0.5guaranteescap × 2 < window, i.e. ≥2 groups always fit), with a 32K floor:Configurable:
HIVE_MIND_MAX_OUTPUT_COMPACTION_FRACTION(default0.45;0disables) andHIVE_MIND_MIN_OUTPUT_TOKENS(default32000). Full write-up indocs/case-studies/issue-1841/README.md(§0 before/after, §4.1 compaction-config verification, §4.2 backward compatibility & "compact earlier").What changed
src/config.lib.mjscomputeCompactionSafeOutputCap+ knobs, applied ingetClaudeEnvto capCLAUDE_CODE_MAX_OUTPUT_TOKENSatfloor(window × 0.45)(32K floor) so one turn can't dominate the compaction window. AlsoretryLimits.maxContextLimitRestarts(default1, envHIVE_MIND_MAX_CONTEXT_LIMIT_RESTARTS) — bounds restarts so a fresh session that also overflows can't loop expensively.src/tool-retry.lib.mjsclassifyRetryableErrornow flagsPrompt is too long/input is too longandAutocompact is thrashing/rapid_refill_breakerwith{ requiresFreshSession: true, isContextLimit: true }. Centralized, so every tool that uses the classifier benefits (R6).src/claude.context-limit-recovery.lib.mjs(new)createContextLimitRecovery— discards the session and restarts fresh (resuming would replay the same over-long transcript forever in headless mode), after auto-committing uncommitted work. Stateful restart cap. Header documents both failure modes.src/claude.lib.mjsPrompt is too longandAutocompact is thrashing(detected via message orterminal_reason). Thinking-block recovery (#1834) guarded with!isContextLimitso the two recoveries don't collide. Added verbose auto-compaction lifecycle tracing (🗜️ compacting,⚠️ compaction FAILED (compact_error: …),📏 Detected "Prompt is too long"/📏 Detected "Autocompact is thrashing"diagnostics, and📏 Capped per-turn output …).src/exit-handler.lib.mjsuncaughtException/unhandledRejectionhandlers now run the interrupt (auto-commit + push) before cleanup, latched so it runs once — previously they ran cleanup only and could lose uncommitted work. Guarantees "auto-commit on all errors" (PR feedback).docs/case-studies/issue-1841/tests/test-issue-1841-context-limit-recovery.mjsgetClaudeEnvintegration.Why fresh-restart-only (not resume-first)
Unlike the thinking-block recovery (#1834), resuming is futile here: in headless mode the on-disk transcript only grows, so resuming replays the same oversized prompt and the error repeats forever. The only thing that makes progress is a fresh session (equivalent to
/clear, which the thrashing message itself recommends), which re-reads the issue/PR/branch state and continues from the already-committed work — hence auto-commit happens before each restart.Auto-commit on all errors (PR feedback)
Recovery preserves work (commit + push) before each restart, gated by
criticalErrorRecovery.autoCommitUncommittedChanges(on by default,HIVE_MIND_AUTO_COMMIT_ON_CRITICAL_ERROR). The existing failure-exit path already auto-committed; this PR closes the remaining gap so theuncaughtException/unhandledRejectioncrash handlers also auto-commit before cleanup. Work is now never lost on any error class.Backward compatibility (PR feedback)
Every change is additive and gated by existing defaults. The
150k--sub-session-sizedefault is unchanged, so existing runs behave identically; "compact a bit earlier" stays opt-in via--sub-session-size/CLAUDE_CODE_AUTO_COMPACT_WINDOWrather than changing the default (verified in §4.2). The output cap only lowers an over-largeCLAUDE_CODE_MAX_OUTPUT_TOKENSand can be disabled (HIVE_MIND_MAX_OUTPUT_COMPACTION_FRACTION=0).Upstream report (R1/R5)
Root cause is on Claude Code's side for both modes. It is already reported multiple times upstream (anthropics/claude-code#46348, #23751, #26317, #25620, #24976), so a new issue would duplicate. A self-contained, reproducible report draft covering both failure modes is kept at
docs/case-studies/issue-1841/upstream-report.mdin case a maintainer asks for a fresh repro.Reproduce / verify
Requirements checklist
too_few_groupsis avoided where possible, not just recovered from.docs/case-studies/issue-1841/(incl. §0 before/after, §4.1 compaction-config verification, §4.2 backward-compat).classifyRetryableError+getClaudeEnv, so it applies across the codebase.Autocompact is thrashing/rapid_refill_breaker) handled + reported.