Claude Code freezes: static spinner, unresponsive input, ignores SIGTERM

Status Open
Maintainer reply None cached
Activity 6 comments · opened Jan 24, 2026

Description

Claude Code occasionally enters a frozen state where:

  1. The animated spinner becomes static (stops animating)
  2. Input field stops accepting any input
  3. Escape does not cancel the current operation
  4. The process ignores SIGTERM (remains in Rl+ state)
  5. Only SIGKILL terminates it

Reproduction

Observed twice in tmux sessions. Both times the session was running normally, then froze mid-operation.

Case 1: Context exhaustion

  • Context at 5% remaining
  • Status showed: Ideating… (34m 23s · ↓ 0 tokens · thinking)
  • 0 tokens received for 34+ minutes
  • Likely: API rejected the request due to context overflow, but client didn't handle the error/timeout

Case 2: Normal operation

  • Context was normal (not exhausted)
  • Status showed: Pondering… (4m 4s · ↓ 7.7k tokens · thought for 2s)
  • 7.7k tokens were received, then froze
  • The session was managing other tmux sessions via tmux send-keys commands

Environment

  • Platform: Linux 6.8.0-90-generic
  • Claude Code running inside tmux sessions
  • Multiple concurrent Claude Code sessions active

Expected behavior

  • Escape should cancel the current operation
  • SIGTERM should gracefully terminate the process
  • If the API connection drops, the client should timeout and show an error

Actual behavior

  • Complete freeze: no animation, no input, no response to signals
  • Only kill -9 works

Process state when frozen

bash(PID)---claude(PID)-+-{claude}(thread1)
                         |-{claude}(thread2)
                         |-{claude}(thread3)
                         |-{claude}(thread4)
                         |-{claude}(thread5)
                         |-{claude}(thread6)
                         |-{claude}(thread7)
                         |-{claude}(thread8)
                         |-{claude}(thread9)
                         `-{claude}(thread10)

Process state: Rl+ (Running, multi-threaded, foreground)

Hypothesis

Possible deadlock in the Node.js event loop — likely waiting on an SSE stream from the API with a broken/stalled connection, while the terminal I/O thread is also blocked.

View original on GitHub ↗

6 Comments

github-actions[bot] · 7 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/19900
  2. https://github.com/anthropics/claude-code/issues/19344
  3. https://github.com/anthropics/claude-code/issues/20380

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

raphaelweber · 7 months ago

I'm experiencing the same issue. Here's detailed technical analysis:

## Thread State Analysis

Analyzed frozen processes via /proc/<pid>/task/*/wchan:

| Thread Type | State | Count |
|-------------|-------|-------|
| Main thread | ep_poll (waiting for epoll events) | 1 |
| Worker threads | futex_wait_queue (blocked on mutex) | 18 |
| I/O thread | wait_woken | 1 |

## Diagnosis

This is a thread deadlock:

  • Main thread stuck in ep_poll, waiting for events
  • UI thread blocked on futex_wait_queue - waiting for a mutex
  • One thread holds a lock and is waiting for I/O
  • UI thread waits for the same lock → deadlock

Process shows State: R (running) because one thread is active, but the event loop
never runs.

## Concrete Reproduction Trigger

Bash tool execution reliably triggers the freeze. Specific example:

stty -F /dev/ttyUSB0 115200 raw -echo && timeout 8 cat /dev/ttyUSB0 2>&1 || true

However, the freeze also occurs with non-blocking commands and background workarounds
(command &). Any Bash tool execution can trigger it.

## Debug Log Analysis

With --debug flag enabled, the log (~/.claude/debug/) shows:

  • No errors or warnings before the freeze
  • Last entries are repeated [ToolSearch:optimistic] lines (20+ identical at same

timestamp)

  • Then logging stops completely - no further entries
  • The deadlock occurs synchronously - even the logging thread is blocked

## Additional Evidence

After killing the frozen process with kill -9:

  • Terminal immediately showed buffered keystrokes typed during the freeze
  • These were passed to shell (causing "command not found" errors)
  • Confirms: UI thread was blocked, process alive, input buffered but never read by

Claude Code

## Key Difference from #19344 (GC Death Spiral)

| Metric | GC Death Spiral (#19344) | This Issue |
|--------|--------------------------|------------|
| CPU | 150-180% | Low (no spinning) |
| Thread state | GC functions | futex_wait_queue |
| Memory | Growing | Stable |

This is NOT a GC issue - it's a mutex deadlock in the Bash tool execution path.

## Environment

  • Claude Code 2.1.19
  • Ubuntu 24.04.3 LTS
  • Kernel 6.14.x
  • Terminal: GNOME Terminal (Ubuntu default)

## Frequency

Started a few days ago. Occurs irregularly - sometimes every few minutes, sometimes
every few hours. Multiple freezes during a single debugging session.

Pxmme · 7 months ago

I am experiencing the same issue. Always happens once Claude code has been running in the same session for quite some time. Fully freezes.

gwpl · 6 months ago

AI Assistant:

Additional data: strace confirms stdin reads succeed but TUI silently drops all input

Reproduced on both v2.1.37 and v2.1.39 — the v2.1.39 fixes ("process hanging after session close", "fatal errors being swallowed") do NOT address this.

Environment

  • Claude Code: 2.1.37, then upgraded to 2.1.39 — same bug
  • Node: v22.19.0
  • OS: Linux 6.17.3-arch2-1 (Arch Linux, x86_64)
  • Terminal: tmux 3.5a
  • Conversation: ~195MB JSONL, 20K+ lines, resumed session (--resume)
  • ~88 other claude instances running on same machine — all working fine

Reproduction pattern

Both freezes followed the same sequence:

  1. A Bash tool command times out at 30s returning empty output
  2. Claude makes a new API call (spinner shows "↑ N tokens")
  3. The API streaming connection silently drops
  4. TUI becomes completely unresponsive — frozen spinner, frozen timer
  5. The prompt renders at the bottom but is non-functional

What strace revealed (conclusions)

Ran strace -p <pid> -e trace=read,write -f -t as root on both stuck processes. Key findings:

  1. Main thread is blocking on read() from the terminal PTY — this is the normal stdin-reading state, so the event loop is alive and waiting for input.
  1. Escape key is read successfully but has zero effect — sent ESC via tmux send-keys, strace shows read(fd, "\33", ...) = 1 (one byte read successfully). The TUI does not react at all. Identical result on both v2.1.37 and v2.1.39.
  1. Ctrl+C and text+Enter also consumed and silently discarded — all keyboard input reaches the process via read(), but the TUI state machine drops everything.
  1. Zero TCP connectionsss -tnp | grep pid=<PID> returns empty. The API connection is completely gone — no outbound sockets at all. Only a few localhost LISTEN sockets remain (v2.1.37) or none (v2.1.39).
  1. Worker thread alive but idle — an inotify watcher thread periodically reads history.jsonl changes and signals via eventfd. It's the only active I/O in the process.
  1. Thread wait channels — main thread in wait_woken, one thread in do_epoll_wait (event loop), remaining 8–16 threads in futex_do_wait (idle workers), one in wait_woken (inotify). No thread is stuck on I/O, network, or a lock — they're all just sleeping with nothing to do.

JSONL conversation file stops being written

The conversation JSONL stops updating at the moment the API call is made. The last recorded entry is always a tool_result (the response to the last Bash command). The subsequent assistant API response that would follow is never written — consistent with the streaming connection dying before any data arrives.

Interpretation

The API streaming connection drops silently (likely HTTP/2 stream reset or TCP timeout not propagated). The Node.js HTTP client doesn't fire an error or timeout event. The TUI state machine is stuck in "waiting for API response" mode. In this mode, it reads stdin bytes but discards them — neither Escape (documented interrupt) nor Ctrl+C work.

The event loop has no pending I/O (no sockets, no timers), so it just sleeps on epoll with nothing to wake it up. The process is effectively a zombie: alive, reading stdin, but unable to process input or make progress. Only kill -9 works.

Suggested fix areas

  1. API streaming timeout — if no data received for N seconds, abort and surface error to the TUI
  2. TUI input handling — Escape/Ctrl+C should work regardless of internal state (e.g., check for interrupt before dispatching to state-specific handler)
  3. Connection health monitoring — periodic TCP keepalive or application-level ping to detect dead connections

I have full sanitized strace logs, /proc dumps (thread wchans, fd maps, memory stats), and pane captures available if any of these would be helpful for debugging. Happy to share on request.

matthieugomez · 4 months ago

Same bug on macOS ARM64, v2.1.92 (April 2026).

Frozen process had zero TCP connections and was 94% idle in kevent64 — waiting on a dead connection. Happens ~once/day on a university network with dynamic IP. Full write-up: #45269

efloehr · 4 months ago

Hit the same symptom fingerprint on Linux today, but the diagnostic data points at a different mechanism than the SSE hypothesis. Adding it here in case it helps narrow down the class.

Environment

  • Claude Code 2.1.114
  • Node v22.22.0 (via fnm)
  • Linux 6.8.0-101-generic (Ubuntu), PREEMPT_DYNAMIC
  • Running in tmux (8 parallel Claude instances, one per window — only one hung)

Symptom (identical to OP)

  • Static spinner frozen at 2m 33s indefinitely
  • Rl+ state, ignores Escape, Ctrl-C, SIGTERM
  • Only SIGKILL recovered
  • 1h47m wall clock frozen before I killed it

What's different from the SSE hypothesis

The hang happened during a purely local Bash tool call — a Node build piped through tail, roughly npm run build 2>&1 | tail -80 (foreground, not backgrounded). No network I/O was in flight. /proc diagnostics on the hot thread:

State:   R (running)
%CPU:    99.9
wchan:   0
syscall: <empty>   # no syscall in progress

wchan=0 + empty syscall = pure-userspace spin, not blocked on I/O. Every other thread was idle:

HeapHelper × 7   → futex_wait_queue
Bun Pool × 9     → futex_wait_queue
HTTP Client      → ep_poll
File Watcher     → wait_woken

So only the main event-loop thread was spinning — looks like a runaway JS loop (regex catastrophic backtracking or similar in the stdout handler), not a stalled SSE reader.

Likely trigger: orphaned pipe from a zombie subprocess

The build's wrapper bash was a zombie (Z state, [bash] <defunct>) — the build had already died over an hour before I killed Claude. Its stdout pipe was apparently still held open by a detached grandchild (Vite spawns esbuild as a persistent service process), and Claude's parent never processed the close event.

Reproduction hypothesis:

  1. Claude runs a $cmd 2>&1 | tail -N foreground Bash tool
  2. $cmd spawns a long-lived detached child that inherits the stdout FD (e.g., Vite's esbuild service)
  3. The outer pipe stays open past the parent's exit
  4. Something in the stdout-tail handler (regex? ANSI parser?) goes pathological on a specific chunk of build output before the close would have arrived
  5. Event loop wedges → signal handlers never fire → SIGKILL required

Signal-handling note

Symptoms align with Node's signal-through-event-loop model: a userspace-starved main thread can't deliver JS-level signal handlers, so Ctrl-C and SIGTERM both become no-ops. Whatever internal watchdog Claude Code has didn't trigger either — suggesting it also runs on the main loop rather than a worker thread.

Session JSONL survived

Last flush was ~2 minutes after session start; killed 1h47m later. Resume-via-disk works fine; only the in-flight (dead) tool output was lost.

Happy to share the full diagnostic bundle (per-thread /proc state, 2000-line pane scrollback, top -H snapshot, process tree) if it'd be useful.