anthropics / anthropics/claude-code
[BUG] Read tool_result never delivered: session and in-process teammate both wedge with the event loop idle in kevent64 (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, matches current npm `latest`)
Related but not identical, cross-referenced deliberately: #91648 (same defect shape on Windows/Bash), #83848 (subagent transcripts stopping after a `tool_use` with no `tool_result`), #92789 (idle-not-crashed session hang on Linux desktop). I believe these share a root cause. What I add here is `sample` evidence showing the event loop is **idle with nothing registered**, plus a case where a parent and its in-process teammate wedged independently in the same OS process.
### What's Wrong?
A `Read` tool call's `tool_result` is never delivered. The turn's continuation never settles, and the process goes fully idle rather than erroring or timing out. The elapsed-time counter keeps incrementing, so it is indistinguishable from slowness.
This happened twice within one OS process: first in a background in-process teammate, then in the parent session 4.5 minutes later.
**The process is idle, not deadlocked and not spinning.** `sample` of the wedged process, taken twice ~5 minutes apart, identical in shape both times:
```
3213 Thread_32466316 DispatchQueue_1: com.apple.main-thread (serial)
! : 3148 kevent64 (in libsystem_kernel.dylib) + 8
```
3148 of 3213 samples on the main thread are in `kevent64`, the idle event-loop wait. Every `Bun Pool` worker thread is parked in `__ulock_wait2`. CPU steady at ~1%. No thread is blocked in a file or socket syscall and no mutex is contended on the main thread. Nothing is registered that could ever wake the turn, which is why waiting cannot recover it.
**No API request is in flight.** `lsof -nP -i -a -p ` shows zero sockets in `ESTABLISHED`.
**Exactly one orphaned `tool_use` per agent.** Counting `tool_use` ids against `tool_result` ids in the transcripts:
| Agent | `tool_use` | `tool_result` | orphaned |
|---|---|---|---|
| Parent session | 71 | 70 | 1 |
| In-process teammate | 8 | 7 | 1 |
Both orphans are `Read`, both targeting small local files on APFS:
```
parent 2026-09-14T09:31:30.991Z Read <26,618-byte local .md>
teammate 2026-09-14T09:26:58.200Z Read <27,445-byte local .md>
```
`isApiErrorMessage` entries in either transcript: **0**.
**Out-of-order completion in the teammate.** It emitted two `Read` calls in one assistant message, 203ms apart. The *second* returned in 15ms; the *first* never returned:
```
09:26:58.200 assistant tool_use:Read(ORPHAN) <- never returns
09:26:58.403 assistant tool_use:Read(SIBLING)
09:26:58.418 user tool_result(SIBLING) <- returns in 15ms
```
That is the signature of a lost result in parallel tool-call fan-in.
**Teammate metadata** (from the subagent `.meta.json`):
```json
{"taskKind": "in_process_teammate", "requestShape": "background",
"requestNonInteractive": true, "permissionMode": "auto", "spawnDepth": 0}
```
The teammate wedged first; the parent wedged on its next tool call 4.5 minutes later. Since an in-process teammate shares the parent's event loop, a dropped continuation in one branch appears able to strand the other.
### What Should Happen?
A `tool_use` with no `tool_result` should hit a timeout and surface a tool error, letting the turn continue or fail visibly. It should never leave the session in a state where the event loop is idle with no pending work and no path to recovery.
### Error Messages/Logs
There are none, and that is the core of the report. No error in the UI, no error in the transcript, no timeout, no retry. The only way to distinguish this from ordinary slowness is to `sample` the process and notice the main thread is in the idle `kevent64` wait with nothing registered.
Secondary symptom: pressing Esc returns to the prompt, but subsequent prompts also produce no output, which suggests the agent loop's state does not fully reset after the cancel.
### Steps to Reproduce
I do not have a deterministic repro, so I am reporting the conditions and the measurement method instead.
Conditions present in both occurrences:
1. A long-running interactive session (~127.7k tokens of context at the time of the hang).
2. One background in-process teammate spawned via the Agent tool, running concurrently in the same OS process.
3. A `Read` on a small local file, dispatched while that teammate was live.
To detect it rather than guess at it:
1. `sample 5` on the apparently-stuck process. If the main thread is in `kevent64` and the worker threads are in `__ulock_wait2`, it is idle, not busy.
2. `lsof -nP -i -a -p ` and confirm zero `ESTABLISHED` sockets, so no request is in flight.
3. In the session `.jsonl`, collect every `tool_use` id and every `tool_result` `tool_use_id` and diff them. The orphan is the dropped call.
### Claude Model
`claude-opus-5` (parent), `claude-sonnet-5` (in-process teammate)
### 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 (T6041). Runtime is Bun; worker threads appear as `Bun Pool N`.
**Ruled out by testing, please do not treat these as the cause:**
- *File or filesystem.* Both target files read instantly from another session afterward. Local APFS, not a File Provider or network mount. No stuck `fileproviderctl`, no processes in `U` state.
- *File size.* In the same session a 356,726-byte PDF and a 315,945-byte PDF read successfully, and a 25,777-byte markdown read successfully twice. The orphans were 26,618 and 27,445 bytes. No size threshold.
- *Permissions.* An `allow` rule covered both paths and auto mode was on. 70 of 71 calls in the same session succeeded, including a read in the same directory as the teammate's orphan.
- *Host resource exhaustion.* 63% memory free, 2.31MB of 1024MB swap used, 1303 of 8000 processes for the uid.
- *Hung hook or child process.* The only children were `caffeinate` and two idle MCP servers.
- *Socket state.* The three `CLOSED` FDs are ordinary HTTP-pool residue; healthy sessions on the same machine had 14 and 5.
**Suggested fix direction:** a watchdog on pending `tool_use` ids. If a `tool_use` has no `tool_result` after N seconds and the event loop has nothing pending, surface a tool error instead of waiting forever. That alone converts a silent unrecoverable hang into a recoverable error, independent of whatever drops the result.
**Update (after two further failure modes were found):** this fix direction is too narrow. Two other paths reach the same idle event loop with **no** pending `tool_use` at all -- #94261 (compaction stalls permanently at 95%) and #94335 (the `tool_result` IS delivered and the turn still never continues). A watchdog keyed on outstanding tool calls catches only this issue. The formulation that covers all three is "a turn is marked in progress while the event loop has no registered work for N seconds". Note also that this failure is invisible from the parent side when it happens in a background agent: the `Agent` tool's `tool_result` is the immediate "Spawned successfully" string, so the parent's pending-call bookkeeping always balances no matter what happens to the agent afterwards.
**Impact:** the session is unrecoverable in place and queued user messages are stranded, though they remain readable in the transcript as `queue-operation`/`enqueue` records and `--resume` restores state through the last complete turn.
Paths, hostnames, repository names, session ids and tool-use ids in this report are replaced with placeholders. Byte sizes, timestamps and counts are unmodified.
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Piste de recherche
Start by reviewing the session .jsonl records and reproducing the diagnostic steps with sample and lsof to compare orphaned tool_use entries with tool_result entries. Trace the in-process teammate and parent turn lifecycle around the idle event loop; done means a missing continuation no longer leaves the session unrecoverable and instead produces a visible, recoverable outcome.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- aws, bun, macos
- Domaine
- cli, devtools, tooling
- Type d'issue
- Bug
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 35/100