anthropics / anthropics/claude-code
[BUG] Compaction stalls permanently at 95%: event loop goes idle in kevent64 with zero orphaned tool calls (macOS, 2.1.270)
- Langage dominant
- Python
- Étoiles
- 145k
- Forks
- 23.1k
- Métriques de merge des PR
- Métriques de PR en attente
Description
### Preflight Checklist
- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue+state%3Aopen+label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] 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 ` 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 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 ` 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 #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 #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.
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Piste de recherche
Start with the agent loop and compaction path; the issue names no source files or tests. Use the session .jsonl and subagents/*.jsonl together with two sample runs and lsof to distinguish this case from #94252. Done means a stalled accepted turn either completes or surfaces a recoverable error, with a diagnostic marker for compaction start.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- aws, bun, macos
- Domaine
- cli
- Type d'issue
- Bug
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Active
- Clarté
- À clarifier
- Accessibilité débutants
- 30/100