Skip to content

[BUG] Compaction stalls permanently at 95%: event loop goes idle in kevent64 with zero orphaned tool calls (macOS, 2.1.270) #94261

Description

@SnoElement

Preflight Checklist

  • 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:

parent session   983 tool_use / 983 tool_result / 0 orphaned

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:

  1. A long, analysis-heavy session with many completed compactions already (6418 transcript entries, 983 tool calls, dozens of subagents over several days of --resume).
  2. Auto-compaction triggering on a user turn rather than an explicit /compact.
  3. No unusual load: CPU low, memory free, no swap pressure.

To identify it rather than guess:

  1. sample <pid> 5 twice, ~20s apart. Compute the main thread's kevent64 sample 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.
  2. lsof -nP -i -a -p <pid> and confirm no ESTABLISHED socket.
  3. In the session .jsonl, diff every tool_use id against every tool_result tool_use_id, including subagents/*.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.
  4. Confirm the last conversational entry is an unanswered user entry.

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:

  • A dropped tool result. 983 of 983 tool calls returned. This is the measured difference from [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.
  • 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    api:bedrockIssue relates to AWS Bedrockarea:corebugSomething isn't workingplatform:macosIssue specifically occurs on macOS

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions