[BUG] Claude Code hangs mid-session after extended tool-use; prompt disappears, SIGTERM ignore

Resolved 💬 3 comments Opened Apr 17, 2026 by fuocor Closed Apr 21, 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?

Summary

A long-running Claude Code CLI session (~1 day) hung indefinitely during a
sequence of code-edit + git-commit tool calls. The session had a legitimate
background monitor subprocess running throughout (a tail -F | grep … pipeline
reporting log lines periodically). After many successful commits, the session
became unresponsive — the input prompt vanished. The claude process was
alive but stuck in Sl+ sleep state at ~0% CPU. SIGTERM was ignored; only
SIGKILL terminated it. After SIGKILL, the terminal was left in raw mode and
stty sane typed blind did not recover it; the whole terminal window had to
be closed.

This is distinct from the well-known "tool call taking too long" cases — the
session had been productive for hours, committed ~24 commits successfully,
and the hang occurred between turns, not during a specific single long-running
command.

## Environment

  • Claude Code CLI, launched from a Cursor-hosted bash shell on Ubuntu 22.04.
  • Shell ancestry: systemd → bash → sh → node → node → bash → claude.
  • Terminal: pts/6, owned by the outer bash (PID 987091).
  • Session PID was 987602 (uptime at hang: ~1 day 5 h, CPU 7.4% averaged,

~0% at hang time).

## What I was doing

  1. Asked Claude to set up a background monitor of a long-running roadmap log

file and report status periodically. Claude launched:
``
tail -F /tmp/crucible_greenfield_v2_plan.log \
| grep -v "tool_use #" \
| grep -E --line-buffered "Architect: |verdict=|Judge |Roadmap (complete|failed|spec written)|Pipeline cost|Traceback|^fatal:|CRITICAL|^Error:"
``
This worked correctly — I saw periodic status reports as the tailed log
accumulated lines.

  1. Asked Claude to make code changes to the repo (non-trivial refactor

across multiple files in agents/, common/, tests/, and docs/).

  1. Claude worked through the task. Over the course of the session it

produced 24 successful commits spanning multi-hour work — I can share
the full git log range (698d89c..9cbc7e1) privately.

  1. At some point mid-task, the CLI stopped responding. My input no longer

rendered. The process was still there; it just wasn't accepting
anything.

## Evidence gathered after the hang

### Process state

``
$ ps -o pid,ppid,pgid,sid,stat,cmd -p 987602
PID PPID PGID SID STAT CMD
987602 987091 987602 987091 Sl+ claude
``

Sl+ — foreground, multithreaded, sleeping. 0.0% CPU when checked.
Process had been alive for >1 day.

### The monitor was still attached and healthy

``
$ pstree -p 987602
claude(987602)-+-bash(1960472)-+-grep(1960475)
| |-grep(1960476)
|
-tail(1960474)
|-bash(2318468)---crucible(2318471)-+-claude(2350063)-…

```

So the hung claude had:

  • The monitor pipeline (bash → tail, grep, grep) as one child subtree.
  • A separately-launched long-running worker (bash → crucible → claude-agent-sdk)

as another. The worker was still fully alive and productive (it kept
writing to its own log, making API calls, etc.) while the parent claude
CLI was stuck.

### On-disk state of the in-progress work

When I inspected the working tree:

  • All substantive edits had already been committed (24 commits from the

session, pushed to origin — nothing lost there).

  • One file (docs/planner_pipeline.md) had a **substantial, structurally-

complete** uncommitted change: +146 / −43 lines, mtime 2026-04-17 14:25:36.

  • Markdown fence count even (16), no truncated sentences, no half-written

code blocks. The diff reads as "finished authoring, not yet committed."

So the hang was between tool calls — Claude had finished its edits and
was presumably about to commit (or was composing the next message) when it
froze.

### SIGTERM was ignored

``
$ kill 987602 # SIGTERM
$ sleep 2 && ps -p 987602 # still alive, unchanged state
... Sl+ claude
$ kill -9 987602 # SIGKILL
$ ps -p 987602 # gone
``

### Terminal left in raw mode

After SIGKILL, my shell (bash 987091) was still alive on pts/6 but the
terminal was in raw mode — no echo, no line buffering. Blind-typing
stty sane<Enter> did not recover it. Blind-typing reset<Enter> did not
recover it. I had to close the terminal window entirely.

## What this looks like from outside

  • A productive Claude Code session silently transitions into a deadlocked

state between tool calls, after hours of successful work.

  • No user-facing error. Prompt just stops echoing.
  • Signals say the process is sleeping on something that never wakes up.
  • SIGTERM doesn't help; cleanup handlers don't run on SIGKILL, so the

terminal TTY settings are never restored.

I cannot tell from outside whether the deadlock is:

  • a blocked read on a subprocess pipe (the monitor? some other child?),
  • a deadlocked mutex between the Bash tool and the file watcher,
  • a hung API call that isn't honouring its timeout,
  • something in the node event loop,

…but the observable symptoms are consistent across all of them: silent,
unrecoverable, SIGTERM-deaf.

## Suggested fixes (prioritised)

  1. Install a SIGTERM handler that at minimum:
  • Calls tcsetattr to restore cooked terminal mode.
  • Flushes any in-flight conversation state to disk.
  • Exits within a bounded grace period, then upgrades to SIGKILL.

This alone would convert an unrecoverable hang into a routine kill.

  1. Heartbeat / watchdog on the main loop. If no tool call, no stream

event, and no user input for N seconds while the process is nominally
waiting, emit an internal timeout and surface it to the user as "session
stalled; recovering / reconnecting / exiting." SIGTERM-deaf silent hangs
are the worst failure mode for a user-facing CLI.

  1. **Separate long-running background subprocesses from tool-call

subprocesses.** A monitor like tail -F | grep | grep is fundamentally a
"fire and let it run, sample periodically" task, not a bounded tool
invocation. If the CLI launches it via the normal tool path and keeps a
pipe open to it forever, any back-pressure on that pipe can ripple into
the main event loop. Detach (setsid … & disown, redirect to a file,
expose a "read monitor output" capability for sampling) rather than
holding an open pipe.

  1. Always tcsetattr on abnormal exit paths. Even if the SIGTERM

handler lands, crashes and SIGKILL will still leave the TTY broken.
Consider having the parent shell-launcher script (or the CLI itself)
register an external stty sane trap so that merely the claude
process dying is sufficient to restore the terminal.

  1. Optional: a /status or /diagnose key chord that forcibly

interrupts the current wait and prints the internal state — what tool
call is in flight, how long, which subprocesses are alive, where the
API call is. Would have saved the ~30 min of pstree forensics in this
case.

## What worked for recovery

  • kill -9 987602 (SIGKILL).
  • Closing the terminal window (bash 987091 had the same raw-mode problem

even after the child was gone).

  • A fresh terminal restored normal state.
  • **The detached long-running worker (crucible roadmap create, PID 2318471)

and its LLM subprocess were completely unharmed — they had their own
session (setsid-like parent relationship), stdin redirected to
/dev/null, stdout to a file. When I killed the stuck claude parent,
kernel reparented them to init and they kept running productively.
That pattern is the one to copy:
never keep a pipe open to a long-
running background subprocess from the main event loop.**

What Should Happen?

I should not hang

Error Messages/Logs

Steps to Reproduce

see above

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.113

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

VS Code integrated terminal

Additional Information

_No response_

View original on GitHub ↗

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