You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] Compaction stalls permanently at 95%: event loop goes idle in kevent64 with zero orphaned tool calls (macOS, 2.1.270) #94261
I have searched existing issues and this hasn't been reported yet
This is a single bug report (please file separate reports for different bugs)
I am using the latest version of Claude Code (2.1.270, confirmed as the version actually running, not just the installed one)
Split out of #94252 at the point where the evidence diverged. That issue is a dropped tool_result leaving the event loop idle. This one reaches the same idle-event-loop state with nothing dropped at all, triggered by compaction. Same terminal state, different entry point, so it likely needs a different fix.
What's Wrong?
Auto-compaction reaches 95% in the UI and stops there permanently. The percentage never advances, no error appears, and the session never produces the assistant turn it was compacting for. The process goes fully idle rather than failing.
Observed three times in about two hours across two different sessions on 2026-09-14, on 2.1.270.
Nothing is dropped. Counting tool_use ids against tool_result ids in the session transcript and every subagent transcript:
This is the key difference from #94252, where exactly one tool_use per agent had no tool_result.
Compaction is not broadly broken. The same session had already completed 8 compactions successfully, 6 of them within the preceding five hours, the most recent 33 minutes before the stall. All 8 are present in the transcript as entries carrying isCompactSummary: true and compactMetadata.
The process is idle, not spinning and not deadlocked. Two sample runs 20 seconds apart:
sample 1 main thread 98% of samples in kevent64
sample 2 main thread 97% of samples in kevent64
cpu +0.68s over a 20s window
Every worker thread is parked in __ulock_wait2. lsof -nP -i -a -p <pid> shows zero sockets in ESTABLISHED, so no request is in flight.
Note for anyone measuring this: a healthy session idling at the prompt also samples ~98% in kevent64, so the idle stack alone does not establish a hang. It establishes that the process is not busy and that waiting cannot recover it.
What establishes the hang is the transcript tail. The final conversational record is a user entry with no assistant entry after it, followed only by metadata records:
10:32:23.438Z system turn_duration
10:35:26.936Z system away_summary
10:36:00.578Z user <- accepted, never answered
(then only: file-history-snapshot, last-prompt, ai-title, mode, permission-mode)
The user turn is persisted when accepted, before any model call, so an unanswered trailing user entry is durable after-the-fact evidence that a turn was accepted and then abandoned. A session merely idling at the prompt has no pending user turn.
A stalled compaction leaves no trace whatsoever. Completed compactions write isCompactSummary: true; there is no corresponding "compaction started" record, so a compaction that never finishes is invisible in the transcript. The only way to know it happened is to have been watching the UI.
What Should Happen?
Compaction should either complete, or fail with a surfaced error that lets the turn continue or be retried. It should never leave the session with a turn accepted, the event loop idle, and no path to recovery.
Separately: compaction should write a "started" marker so a stall is diagnosable after the process is gone.
Error Messages/Logs
None, which is the core of the report. No error in the UI, none in the transcript, no timeout, no retry. isApiErrorMessage entries: 0.
Esc/cancel does not recover the session. On an earlier occurrence, cancelling returned to the prompt, but subsequent prompts also produced no output, suggesting the agent loop's state does not fully reset.
Steps to Reproduce
I have no deterministic repro, so I am reporting the conditions and the measurement method.
Conditions common to the occurrences:
A long, analysis-heavy session with many completed compactions already (6418 transcript entries, 983 tool calls, dozens of subagents over several days of --resume).
Auto-compaction triggering on a user turn rather than an explicit /compact.
No unusual load: CPU low, memory free, no swap pressure.
To identify it rather than guess:
sample <pid> 5 twice, ~20s apart. Compute the main thread's kevent64sample count as a share of that thread's total. Do not use grep -c kevent64: it returns 3 for wedged and healthy processes alike, because the string appears once in the main thread, once in a helper thread, and once in the by-function summary.
lsof -nP -i -a -p <pid> and confirm no ESTABLISHED socket.
Compaction being broken generally. 8 compactions completed in this same session, 6 within the preceding five hours.
A request in flight. No ESTABLISHED socket. The CLOSED FDs present are ordinary HTTP-pool residue; healthy sessions on the same machine had 14 and 5.
Host resource exhaustion. Memory free, negligible swap in use, process count far below the uid limit.
A stale binary. The running version was read from sample's (in 2.1.270) frames and matches the installed version. Worth flagging generally: claude --version reports the installed version, and a long-lived session keeps running whatever it launched with, so the two can differ.
Suggested fix direction: a stall detector that does not depend on knowing what stalled. If a turn is marked in progress and the event loop has no registered work for N seconds, surface an error and let the turn fail visibly. A watchdog keyed only on pending tool_use ids, which is what I suggested on #94252, would not catch this case, because there is no pending tool_use.
Impact: the session is unrecoverable in place and must be killed. --resume restores state through the last complete turn, and in this occurrence no queued input was lost, though on an earlier occurrence of the sibling bug queued messages were stranded.
Paths, hostnames, repository names and session ids are replaced or omitted. Byte sizes, timestamps, counts and percentages are unmodified.
Preflight Checklist
Split out of #94252 at the point where the evidence diverged. That issue is a dropped
tool_resultleaving the event loop idle. This one reaches the same idle-event-loop state with nothing dropped at all, triggered by compaction. Same terminal state, different entry point, so it likely needs a different fix.What's Wrong?
Auto-compaction reaches 95% in the UI and stops there permanently. The percentage never advances, no error appears, and the session never produces the assistant turn it was compacting for. The process goes fully idle rather than failing.
Observed three times in about two hours across two different sessions on 2026-09-14, on 2.1.270.
Nothing is dropped. Counting
tool_useids againsttool_resultids in the session transcript and every subagent transcript:This is the key difference from #94252, where exactly one
tool_useper agent had notool_result.Compaction is not broadly broken. The same session had already completed 8 compactions successfully, 6 of them within the preceding five hours, the most recent 33 minutes before the stall. All 8 are present in the transcript as entries carrying
isCompactSummary: trueandcompactMetadata.The process is idle, not spinning and not deadlocked. Two
sampleruns 20 seconds apart:Every worker thread is parked in
__ulock_wait2.lsof -nP -i -a -p <pid>shows zero sockets inESTABLISHED, so no request is in flight.Note for anyone measuring this: a healthy session idling at the prompt also samples ~98% in
kevent64, so the idle stack alone does not establish a hang. It establishes that the process is not busy and that waiting cannot recover it.What establishes the hang is the transcript tail. The final conversational record is a
userentry with noassistantentry after it, followed only by metadata records:The user turn is persisted when accepted, before any model call, so an unanswered trailing
userentry is durable after-the-fact evidence that a turn was accepted and then abandoned. A session merely idling at the prompt has no pending user turn.A stalled compaction leaves no trace whatsoever. Completed compactions write
isCompactSummary: true; there is no corresponding "compaction started" record, so a compaction that never finishes is invisible in the transcript. The only way to know it happened is to have been watching the UI.What Should Happen?
Compaction should either complete, or fail with a surfaced error that lets the turn continue or be retried. It should never leave the session with a turn accepted, the event loop idle, and no path to recovery.
Separately: compaction should write a "started" marker so a stall is diagnosable after the process is gone.
Error Messages/Logs
None, which is the core of the report. No error in the UI, none in the transcript, no timeout, no retry.
isApiErrorMessageentries: 0.Esc/cancel does not recover the session. On an earlier occurrence, cancelling returned to the prompt, but subsequent prompts also produced no output, suggesting the agent loop's state does not fully reset.
Steps to Reproduce
I have no deterministic repro, so I am reporting the conditions and the measurement method.
Conditions common to the occurrences:
--resume)./compact.To identify it rather than guess:
sample <pid> 5twice, ~20s apart. Compute the main thread'skevent64sample count as a share of that thread's total. Do not usegrep -c kevent64: it returns 3 for wedged and healthy processes alike, because the string appears once in the main thread, once in a helper thread, and once in the by-function summary.lsof -nP -i -a -p <pid>and confirm noESTABLISHEDsocket..jsonl, diff everytool_useid against everytool_resulttool_use_id, includingsubagents/*.jsonl. Zero orphans plus an idle loop is this bug; one orphan is [BUG] Turn goes permanently idle (event loop idle in kevent64, no error): dropped tool_result, stalled compaction, or queued input never taken up (macOS, 2.1.268 to 2.1.283) #94252.userentry.Claude Model
claude-opus-5(parent),claude-sonnet-5(subagents)Is this a regression?
I don't know
Last Working Version
(not known)
Claude Code Version
2.1.270 (Claude Code)
Platform
AWS Bedrock
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
macOS 27.0 (build 26A428), Darwin 27.0.0, xnu-13432.1.9, arm64. Runtime is Bun; worker threads appear as
Bun Pool N.Ruled out by testing, please do not treat these as the cause:
ESTABLISHEDsocket. TheCLOSEDFDs present are ordinary HTTP-pool residue; healthy sessions on the same machine had 14 and 5.sample's(in 2.1.270)frames and matches the installed version. Worth flagging generally:claude --versionreports the installed version, and a long-lived session keeps running whatever it launched with, so the two can differ.Suggested fix direction: a stall detector that does not depend on knowing what stalled. If a turn is marked in progress and the event loop has no registered work for N seconds, surface an error and let the turn fail visibly. A watchdog keyed only on pending
tool_useids, which is what I suggested on #94252, would not catch this case, because there is no pendingtool_use.Impact: the session is unrecoverable in place and must be killed.
--resumerestores state through the last complete turn, and in this occurrence no queued input was lost, though on an earlier occurrence of the sibling bug queued messages were stranded.Paths, hostnames, repository names and session ids are replaced or omitted. Byte sizes, timestamps, counts and percentages are unmodified.