[BUG] Claude Code CLI hangs indefinitely after a successful tool_use response is lost in streaming receive

Resolved 💬 4 comments Opened Apr 25, 2026 by kimesbenjorgensen Closed Apr 29, 2026

Preflight Checklist

  • [x] I have searched existing issues 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

What's Wrong?

Claude Code CLI hangs indefinitely after a successful tool_use response is lost in streaming receive

Closest existing issues: #46767 ("missing tool result" regression). Also related: #47996, #50727, #50185, #44783, #52544. None appear fully fixed — symptoms keep resurfacing.

Summary

When Claude Code runs in a tmux pane and executes a tool call that completes successfully server-side, there is a window in which the CLI can lose the streamed tool_result and the UI remains in the Running… / Undulating… (Ns · ↓ N tokens) state forever. The HTTP connections stay healthy and idle (no kernel-side queued data), the main thread enters a low-CPU livelock (wakes ~100×/sec in epoll_wait with no forward progress), and no transcript events are written. Pressing ESC in the terminal cleanly cancels the in-flight receive; the CLI then re-syncs and reports the operation had already succeeded.

Actual

  • UI stuck on Running… indefinitely. Receive indicator shows a fixed byte count (e.g. ↓ 23.5k tokens) for hours with no progression.
  • Queue events (queue-operation/enqueue) accumulate as the user types into the stuck prompt, but are never dequeued.
  • No transcript events are written for the duration of the hang.
  • ESC cancels the stuck receive; the CLI then discovers the tool had already succeeded and continues normally.

Forensic evidence

Captured while hung (before sending ESC):

| Source | Shows |
|---|---|
| /proc/<pid>/status, thread stacks | Main thread in do_epoll_wait; 11 threads; process alive |
| lsof + ss -tnpo | 4 ESTABLISHED TLS connections to the API endpoint with zero Recv-Q / Send-Q — kernel sees no buffered data |
| /proc polling (6 samples, 2s apart) | ~8% main-thread CPU, ~10% total over 20s. Normal idle CLI is < 1% |
| Process tree | tmux pane still attached; pty intact; not a detached-pty scenario |
| Transcript .jsonl | Last event before hang is the tool_use at 19:31:55; no further writes for 2h13m |

Empty Recv-Q / Send-Q on all four connections is the key data point: there is no buffered data for the CLI to read. Either the server's response was already fully consumed at the kernel-user-space boundary (and something in the CLI failed to process it), or it never arrived and no timeout fires.

Happy to share full sanitized captures (proc-state, thread-stacks, fd-and-sockets, CPU-poll samples) if useful — drop a note here.

Hypotheses

  1. Race in SSE / streaming-response parser. A final event (likely the message_stop or equivalent) arrives concurrently with an internal state transition and is dropped; the fetch promise neither resolves nor rejects.
  2. Timer drift in keepalive / watchdog. The CLI's own watchdog for stuck requests may be reset by the process's hot-polling loop and never fires.
  3. Interaction with tmux SIGWINCH or raw-mode toggle. Two /dev/pts/* fds were opened around the time the hang set in (prior to the triggering tool call), suggesting a raw-mode / signal-handler pipe manipulation. A dropped signal could leave an event-loop handler dangling.

Suggested mitigations

  1. Hard deadline on streaming receives. If no bytes arrive for N seconds on an in-flight tool_result receive, abort with an error the UI can surface and optionally auto-retry.
  2. Dispatch-layer healthcheck. The main event loop waking ~100×/sec with no progress is a diagnosable signature; a lightweight heartbeat comparing expected-advancing state vs. observed could detect the livelock and emit diagnostics.
  3. Persist enough state that an in-flight receive can resume after an ESC. Today ESC simply drops the receive; the CLI then relies on re-sending the context. For long tool results this is wasteful. A checkpoint of last-seen server-side event id would let the client resume.

Notes

  • No destructive action was required — tmux send-keys -t <pane> Escape cleaned up the state fully.
  • A user-side watchdog now auto-detects and auto-recovers this specific pattern (sends Escape via tmux send-keys). That is a workaround, not a fix.
  • After the recent CLI upgrade this hang has become very common in our environment — multiple times per hour during active use. Filing now because the watchdog can mask the symptom but not the root cause.

What Should Happen?

  • After a successful tool invocation, the tool_result is delivered and the conversation continues.
  • If the streaming receive fails, the CLI should surface an error or auto-retry, not hang silently.

Error Messages/Logs

Not deterministic on demand. Organic reproduction captured 2026-04-24 on:

- CLI version: `2.1.118`
- Platform: Linux (LXC container, Proxmox host, Debian 12)
- Terminal: `tmux` 3.3a
- Session start: 2026-04-23 16:09 CEST
- Hang start: 2026-04-24 19:31:55 UTC (`toolu_01KkzPcqKkt2FBQ6Mt7jHg8S`)
- Hang duration: 2 h 13 m 53 s until `ESC` recovered it at 21:45:48 UTC

The triggering tool call was a compound Bash command of the shape:


git stash push -m "pre-pull stash" <file-a> <file-b> && git pull --rebase && git stash pop


The command succeeded on disk (confirmed via `/status` after recovery: *"Git state: clean rebase (stash/pop completed successfully)"*). The hang was on the client's side of the receive stream.

Steps to Reproduce

  1. Run Claude in a detatched tmux on ubuntu 24
  2. Start /remote-control
  3. Detatch the terminal
  4. Remotely manage via mobile app or desktop app.
  5. Use the session activly for a longer period

Claude Model

Opus

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.119

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

I'm suspecting the hit-rate is higher when auto-mode is not used, but I can't prove that,. Sometimes it works well for 24h, sometimes just a few minutes.

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗