[Bug] Claude hangs indefinitely on simple tasks, requires cancellation to resume
Status Open
Reported on v2.1.87
Maintainer reply None cached
Activity 6 comments · opened Apr 1, 2026
Bug Description
Claude get's stuck a lot recently, thinking minutes about things that are not very complex, immediately snapping out if cancelled and told to continue
Environment Info
- Platform: darwin
- Terminal: ghostty
- Version: 2.1.87
- Feedback ID: 7a265dfe-9c0b-4634-a6fc-1014af785867
Errors
[{"error":"Error: ripgrep exited with code null\n at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:648:2638)\n at emit (node:events:98:22)\n at #maybeClose (node:child_process:766:16)\n at #handleOnExit (node:child_process:520:72)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:11:27.005Z"},{"error":"Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"prompt is too long: 200725 tokens > 200000 maximum\"},\"request_id\":\"req_011CZcF1Pe6NhAxKKzAX1hmZ\"}\n at generate (/$bunfs/root/src/entrypoints/cli.js:11:50895)\n at makeRequest (/$bunfs/root/src/entrypoints/cli.js:50:5435)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:13:08.677Z"},{"error":"Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"prompt is too long: 200725 tokens > 200000 maximum\"},\"request_id\":\"req_011CZcFBoRrMty2nsjQCMvud\"}\n at generate (/$bunfs/root/src/entrypoints/cli.js:11:50895)\n at makeRequest (/$bunfs/root/src/entrypoints/cli.js:50:5435)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:15:29.869Z"},{"error":"Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"prompt is too long: 200725 tokens > 200000 maximum\"},\"request_id\":\"req_011CZcFBu9tdAAnBNbrEDcCk\"}\n at generate (/$bunfs/root/src/entrypoints/cli.js:11:50895)\n at makeRequest (/$bunfs/root/src/entrypoints/cli.js:50:5435)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:15:31.242Z"},{"error":"Error: 400 {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"prompt is too long: 200725 tokens > 200000 maximum\"},\"request_id\":\"req_011CZcFBzReGCmcGLDjWpNUy\"}\n at generate (/$bunfs/root/src/entrypoints/cli.js:11:50895)\n at makeRequest (/$bunfs/root/src/entrypoints/cli.js:50:5435)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:15:32.552Z"},{"error":"Error: ripgrep exited with code null\n at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:648:2638)\n at emit (node:events:98:22)\n at #maybeClose (node:child_process:766:16)\n at #handleOnExit (node:child_process:520:72)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:15:33.210Z"},{"error":"Error: ripgrep exited with code null\n at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:648:2638)\n at emit (node:events:98:22)\n at #maybeClose (node:child_process:766:16)\n at #handleOnExit (node:child_process:520:72)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:16:04.881Z"},{"error":"Error: ripgrep exited with code null\n at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:648:2638)\n at emit (node:events:98:22)\n at #maybeClose (node:child_process:766:16)\n at #handleOnExit (node:child_process:520:72)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:16:24.910Z"},{"error":"Error: ripgrep exited with code null\n at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:648:2638)\n at emit (node:events:98:22)\n at #maybeClose (node:child_process:766:16)\n at #handleOnExit (node:child_process:520:72)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:18:17.029Z"},{"error":"Error: ripgrep exited with code null\n at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:648:2638)\n at emit (node:events:98:22)\n at #maybeClose (node:child_process:766:16)\n at #handleOnExit (node:child_process:520:72)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:18:38.562Z"},{"error":"Error: ripgrep exited with code null\n at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:648:2638)\n at emit (node:events:98:22)\n at #maybeClose (node:child_process:766:16)\n at #handleOnExit (node:child_process:520:72)\n at processTicksAndRejections (native:7:39)","timestamp":"2026-04-01T02:19:07.480Z"},{"error":"Error: ripgrep exited with code null\n at <anonymous> (/$bunfs/root/src/entrypoints/cli.js:648:2638)\n at emit (node:events:98:22)\n at #maybeClose (node:child_process:766:16)\n at #handleOnExit (node:child_process:520:72)\n at processTicksAndRejections (native:…
Note: Content was truncated.
6 Comments
Root Cause Analysis (source-level)
_Traced through the published source and verified independently via Codex review._
The "hang" is a 5-layer failure chain where context silently grows past the API limit, reactive compaction takes minutes, and ripgrep crashes prevent recovery.
---
Layer 1: Proactive compaction silently disabled
File:
src/services/compact/autoCompact.ts(lines 195–222)When either
REACTIVE_COMPACT(gated bytengu_cobalt_raccoon) orCONTEXT_COLLAPSEis enabled,shouldAutoCompact()returnsfalse— proactive autocompact is completely suppressed. The system relies entirely on reactive compact (after the API rejects the request) to catch overflows:This means context grows unchecked until the API returns a 400 error.
Layer 2: Token estimation undercount
File:
src/services/tokenEstimation.ts(line 205)The rough estimator defaults to 4 characters per token:
A file-type-aware variant (
roughTokenCountEstimationForFileType) exists and is used byFileReadTool, but the maintokenCountWithEstimation()(src/utils/tokens.ts:226) uses the generic estimator for tail content. The undercount is concentrated in recent unsent content — especially tool results with dense JSON.Layer 3: Reactive compact model call = the visible "hang"
File:
src/query.ts(lines 1119–1166)When the API returns
"prompt is too long: 200725 tokens > 200000 maximum":reactiveCompact.tryReactiveCompact()is called (line 1120)Reactive compact makes its own model API call to summarize the conversation. For a 200K-token context, this call can take 2–3+ minutes. During this time the user sees Claude "thinking" with zero progress. This is the hang.
Evidence from the error timestamps in this issue:
02:13:08— first "prompt is too long" error02:15:29— second error (2 min 21 sec gap = reactive compact duration)02:15:31–32— rapid retries after compact failed to free enoughLayer 4: Ripgrep
code === nullnot handled gracefullyFile:
src/utils/ripgrep.ts(lines 187–202, 269–273, 323–334)When ripgrep is killed by a signal (timeout → SIGTERM → SIGKILL), the
closeevent fires withcode === null:This generates the misleading
"ripgrep exited with code null"errors visible in the issue's log. The higher-levelripGrep()function (line ~411) does convert some of these toRipgrepTimeoutError, but the raw error propagation from the lower-level helpers can still cause cascade failures.Layer 5: Single-shot recovery guard
File:
src/query.ts(line 1157)After one reactive compact attempt, the guard prevents further attempts within the same turn. If compact succeeded but didn't free enough tokens, the next API call still fails — but no further recovery is possible. The error surfaces via
return { reason: 'prompt_too_long' }(line 1175).The guard resets only on a new turn (lines 275, 1721), which is why "cancel and continue" works — the user's cancellation aborts the hanging compact call, and the fresh turn allows another attempt.
Circuit breaker amplification
File:
src/services/compact/autoCompact.ts(line 260–264)If ripgrep crashes cause 3 consecutive compact failures,
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURESpermanently disables autocompact for the session — creating an unrecoverable state.---
Suggested Fixes (refined after code review)
Fix 1: Better ripgrep signal-kill handling
Don't blanket-return success on signal kills (would hide real timeouts in
ripGrepFileCount()). Instead, fix the misleading error message and preserve signal information:Let the higher-level
ripGrep()decide when partial results are acceptable (it already does for timeout cases).Fix 2: Bounded reactive compact duration
The compact model call should have a hard timeout aligned with the existing 90s idle timeout in
claude.ts:1877. If compact doesn't complete within ~90s, abort and surface the error immediately rather than blocking the user for 2+ minutes with no feedback.Fix 3: Progressive compaction (most impactful)
Replace the boolean
hasAttemptedReactiveCompactwith a bounded counter.compactConversation()already has bounded PTL retries with token-gap-based truncation (lines 227, 450 incompact.ts). Allow 2–3 attempts with progressively more aggressive message-group dropping, calibrated bygetPromptTooLongTokenGap()(errors.ts:104–118). Keep the anti-loop guard around stop hooks.Fix 4: Use file-type-aware estimation for tool results
Instead of changing the global 4-chars/token default (which would increase all estimates by 33%), use
roughTokenCountEstimationForFileTypefor tool result content intokenCountWithEstimation(). This targets the undercount where it matters most without affecting other callers.Fix 5: Emergency proactive compact near the limit
Even in reactive-only mode, add a last-resort proactive check: if estimated tokens > 98% of the effective context window, trigger compaction before the API call. This doesn't conflict with the reactive-only design intent (which suppresses routine compaction) — it's a safety valve for the edge case where reactive compact failed and context keeps growing.
---
Summary
| Layer | Root Cause | Impact | Fix |
|-------|-----------|--------|-----|
| 1 | Proactive compact disabled by feature flags | Context grows unchecked | Fix 5: emergency proactive check |
| 2 | Token estimation undercounts JSON/tool results | Threshold missed | Fix 4: file-type-aware estimation |
| 3 | Reactive compact model call takes 2+ min | User sees hang | Fix 2: hard timeout |
| 4 | ripgrep
code===nullnot handled | Cascade failures | Fix 1: preserve signal info || 5 | Single-shot recovery guard | No retry after failure | Fix 3: progressive compaction |
The most impactful fixes are #2 (timeout — immediately resolves the hang UX) and #3 (progressive compaction — improves recovery success rate).
---
_Analysis based on source code, verified by independent Codex review against the same codebase._
Have noticed the same recently, it just gets stuck for 15 minutes "thinking" and wont move forward until I cancel and resume
has got to be caused by /buddy LMAO (just a human guess)
+1 on the response slowdown. It gives a feeling that opus is either not thinking hard enough or somehow the context is mismanaged. No data to back my claim though.
+1 responses seem extremely degraded in throughput. Based on my current session, I'm averaging ~10.4 tokens/sec, with significant slowdowns as the context grows:
Average: ~10.4 t/s across the full 12-minute window, but dropping to ~4 t/s in the latter half.
Also experiencing this a lot this week. Seems to me it's getting worse, today every single prompt that has to read or edit files gets stuck one or more times.
Im on windows, using terminal in powershell. v2.1.91