Session hangs indefinitely — queued prompt never dispatched (reproduced 5x, 2 unrelated projects)
Session hangs indefinitely — queued prompt never dispatched (reproduced 5x across 2 unrelated local projects)
TL;DR
A Claude Code session (desktop app) periodically becomes permanently
unresponsive: the next user prompt gets recorded as submitted, but noUserPromptSubmit hook ever runs, no model turn starts, no error surfaces.
The backend process stays alive — idle, not crashed, not spinning — for
hours until someone manually kills it (SIGTERM). Reconnecting is not a
reliable fix and can re-hang immediately on the next turn. Reproduced 5
times across 2 unrelated local git repositories, one of which has no
custom Claude Code hooks/settings at all, which rules out
project-specific tooling as the cause.
Environment
- Claude Code version: 2.1.197
- Entry point: claude-desktop (desktop app) in every occurrence
- Model: claude-sonnet-5
- OS: Linux (Ubuntu-based), local machine
- Occurrences span two separate, unrelated local git repositories
(different projects), several branches, across 9 days
The pattern
Every occurrence shares this shape, read from the session transcript JSONL:
- Some turn completes normally — either a
SessionStarthook fires and
completes (matcher compact, resume, or a plain trigger; exitCode:, on the order of 100ms), or a full assistant message ends with
0
stop_reason: "end_turn".
- The user's next prompt is captured in a `{"type":"last-prompt",
"lastPrompt": "...", ...}` marker in the transcript.
- Nothing follows. No
UserPromptSubmithook success/error entry, no
assistant message, no tool call, no error. The transcript file simply
stops growing at that point.
- The backend OS process for the session is either:
- still alive, in state
S(sleeping), all threads blocked on
futex_do_wait / ep_poll / wait_woken — genuinely idle, not
spinning in a hot loop or retry storm; or
- not found running at all — the backend appears to have exited on its
own, leaving the desktop UI pointed at a dead session with nothing
left to even kill.
- No hook subprocess is ever found alive/stuck in the process tree at
inspection time — only long-lived MCP server child processes (where the
project configures any), themselves idle.
- Manually sending
SIGTERMto the backend process always terminates it
(and any MCP-server child processes) cleanly within ~2s, no orphans
left behind.
Occurrences (summarized, identifying details omitted)
| # | Repo | Prompt that stalled | Approx. length | What preceded it | Backend process when found | Custom hooks in this project? |
|---|------|----------------------|-----------------|--------------------|------------------------------|---|
| 1 | Project A | short git-push instruction | ~7 chars | SessionStart (compact), completed OK | Alive, idle, ~3h → killed | Yes, extensive |
| 2 | Project A (same session, reconnected after #1) | short git-push instruction (different wording) | ~6 chars | SessionStart (resume), completed OK | Alive, idle, ~13h → killed | Yes, extensive |
| 3 | Project A (same session, next day) | short "open PR, merge" instruction | ~20 chars | one full successful assistant turn, itself preceded by SessionStart (compact) | No process found at all | Yes, extensive |
| 4 | Project A, different branch/worktree | short "merge it" instruction | ~10 chars | SessionStart, completed OK, essentially same millisecond | Alive, idle, ~2h43m → killed | Yes, extensive |
| 5 | Project B (unrelated repo) | a longer, unrelated planning sentence | ~99 chars | a full successful assistant turn (end_turn), then an auto-generated title event | Alive, idle, ~11h → killed | No — this project has no .claude/settings.json at all |
What we ruled out
Project A has an extensive custom hooks setup (SessionStart,UserPromptSubmit, PreToolUse, PostToolUse, Stop, custom
Node-script hooks). Given the symptom, these were the first suspect for
occurrences 1–4:
- Occurrences #1, #2, #4 had short prompts that would have been guarded
out before any LLM/network call in this repo's UserPromptSubmit hooks
(an internal length threshold skips network-based classification for
very short prompts). Those hooks cannot be the cause for those three.
- The other relevant
UserPromptSubmithook is fully wrapped in
try/catch with fail-open semantics and only does local, synchronous
filesystem/git calls (no network) — an unlikely candidate, though it
lacks an explicit subprocess timeout (a latent risk worth hardening
regardless, not confirmed as this bug's cause).
- No hook process was ever found alive/stuck in the process tree, and no
stale git lock existed, at any of the 5 inspections.
Occurrence #5 is the decisive one: it happened in a completely
different, unrelated project with zero custom Claude Code
configuration — no hooks, no skills, nothing beyond stock Claude Code.
It shows the exact same signature (turn completes → next prompt captured
→ dead silence, idle backend process) as the occurrences in the
heavily-instrumented project, on a long prompt this time (so any
length-based hook guard is irrelevant anyway — there are no hooks at all
in that project). This strongly indicates the bug is in Claude Code / theclaude-desktop entrypoint itself, not in any project-specific tooling.
Impact
- Every occurrence made the session completely unusable: no response, no
error, no visible indication of failure, no automatic recovery — until a
human manually found and killed the OS process from a terminal.
- The desktop app's UI gave no indication in any case that the backend had
stopped making progress (no error, timeout, or retry surfaced).
- Reconnecting after killing the process is not a reliable fix.
Occurrence #2 re-hung on the very next turn immediately after
reconnecting from #1. Occurrence #3 hung again the following day on the
same session. This is a recurring, not one-off, failure mode for
long-lived sessions.
- Diagnosing and recovering each occurrence required manual
ps/kill
work outside the Claude Code UI — not something a typical user could do
unassisted.
Suggested follow-ups
- A client-side timeout/watchdog on the turn-dispatch path, so a stalled
request surfaces as a visible error/retry instead of an indefinite
silent hang. This should apply broadly, not just right after
SessionStart — occurrence #5 stalled after an ordinary completed turn
with no hooks involved at all.
- Surface something in the UI when a queued prompt has not begun
processing after some threshold, distinct from "model is thinking."
- Investigate whether some piece of persisted per-session state (written
around turn completion / SessionStart / the queued-prompt handoff)
can itself get wedged and carry over across backend process restarts —
occurrence #2 re-hung immediately after a fresh process reconnected to
the same session id, which points at state rather than a purely
in-process condition.
- Happy to share full session JSONL transcripts privately if that would
help narrow this down — didn't want to paste raw transcripts (with
internal file/branch names) into a public issue.
Reproduction
Reproduced 5 times, 2 unrelated local projects, several branches, spanning
9 days. No special action was needed to trigger it beyond normal usage
(editing, discussing next steps, running CI) — it appears to be a
low-frequency but real intermittent hang in long-lived claude-desktop
sessions.