Skip to content

Session goes permanently idle after a tool_result is delivered, with zero pending tool calls and no compaction in progress #94335

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)

Third distinct entry point into the same unrecoverable idle state, split out from #94252 (a tool_result that never arrives) and #94261 (compaction stalling at 95%). This one is the hardest of the three to detect, because the tool result arrives normally and nothing is left pending anywhere.

What's Wrong?

A tool call completed, its tool_result was delivered and persisted, and the assistant turn that should have consumed it never ran. The session sat idle for about five minutes producing no output, accepted four further messages into its queue without consuming any of them, and only recovered when I killed the process and restarted it.

The transcript records the result arriving 2 milliseconds after the call:

11:26:27.165Z  assistant  tool_use     ToolSearch
11:26:27.167Z  user       tool_result   <- delivered
11:28:44.410Z  queue-operation  enqueue     <- my message, never consumed
11:30:15.231Z  queue-operation  enqueue     <- re-queued
11:30:16.415Z  queue-operation  enqueue     <- re-queued
11:30:48.467Z  queue-operation  enqueue     <- second message, never consumed
        (process killed and restarted here)
11:31:14.804Z  user       "Continue from where you left off."

Zero orphaned tool calls. Diffing every tool_use id against every tool_result tool_use_id across the session and all of its subagent transcripts:

tool_use 276 / tool_result 276 / orphaned 0   at the moment of the stall

This is what makes it distinct from #94252, where exactly one call had no result. And unlike #94261 there was no compaction in progress: the UI showed no percentage, and the most recent compaction had completed successfully three minutes earlier at 11:23:29Z. The session had completed 2 compactions in total, both fine.

So the pending-work bookkeeping is clean in every direction, and the turn is still dead. Whatever schedules the continuation after a delivered tool_result did not run, and nothing timed out or complained.

What Should Happen?

A delivered tool_result should always be followed by either the next assistant turn or a surfaced error. If the continuation cannot be scheduled, that should fail loudly rather than leaving the session idle and accepting input it will never process.

Queued messages should also not be silently retained by a session that is no longer consuming its queue. Four enqueue records accumulated with no dequeue; from the transcript alone it is not possible to tell which queued messages were ever consumed, because dequeue records carry no content.

Error Messages/Logs

None. No error in the UI, none in the transcript, no timeout, no retry. isApiErrorMessage entries: 0.

Steps to Reproduce

No deterministic repro. Reporting the conditions and the identification method, since this mode is invisible to the checks that identify the other two.

Conditions: a long analysis-heavy session (1021 transcript entries, 276 tool calls, several days of --resume), auto-compaction having completed minutes earlier, no unusual host load.

To identify it:

  1. Confirm the process is idle rather than busy: sample <pid> 5, and compute the main thread's kevent64 sample count as a share of that thread's total. Do not use grep -c kevent64, which 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. Diff tool_use ids against tool_result ids, including subagents/*.jsonl. Zero orphans does not clear the session. For this bug it is zero.
  3. Read the transcript tail. The signature is a tool_use with its matching tool_result present, no assistant entry after it, and then queue-operation enqueue records with nothing consuming them.

A caution on step 2, since it cost me a false positive today: a pending AskUserQuestion looks exactly like a hang from outside. It leaves one orphaned tool_use and a main thread ~98% parked in kevent64. A healthy session waiting at the prompt also reads ~98%, so the idle stack never proves a hang by itself.

Claude Model

claude-opus-5

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, arm64. Runtime is Bun.

Ruled out by testing:

  • A dropped tool result. 276 of 276 calls returned, verified across the session and every subagent transcript.
  • A stalled compaction. The last compaction completed 3 minutes earlier and is present in the transcript with isCompactSummary and compactMetadata. No percentage was displayed.
  • A request in flight. No ESTABLISHED socket.
  • Process death. The process was alive and idle throughout; I killed it deliberately to recover.
  • The specific tool. The stall followed ToolSearch, but that is a single observation and I would not weight it. I also chased and discarded a SendMessage correlation on the sibling issue: SendMessage is the last call in 49.5% of agents that finish normally, so dying after one is less likely than chance.

Suggested fix direction: the same stall detector proposed on #94261, and for the same reason -- it must not be keyed on pending work. All three failure modes reach an idle event loop with a turn in progress, but only one of them leaves a pending tool_use behind. A detector that asks "is a turn marked in progress while the event loop has no registered work" catches all three; anything keyed on outstanding tool calls or on compaction state catches at most one.

Impact: unrecoverable in place. Killing and restarting was the only option, and restarting is what makes this bug expensive rather than merely annoying: the session cannot tell you it is stuck, so the time lost is however long you wait before giving up on it.

Paths, hostnames, repository names and session ids are replaced or omitted. 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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions