[BUG] Claude Code hangs mid-session after extended tool-use; prompt disappears, SIGTERM ignore
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
- 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.
- Asked Claude to make code changes to the repo (non-trivial refactor
across multiple files in agents/, common/, tests/, and docs/).
- 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.
- 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
`` -tail(1960474)
$ pstree -p 987602
claude(987602)-+-bash(1960472)-+-grep(1960475)
| |-grep(1960476)
|
|-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)
- Install a SIGTERM handler that at minimum:
- Calls
tcsetattrto 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.
- 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.
- **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.
- Always
tcsetattron 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.
- Optional: a
/statusor/diagnosekey 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_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗