Skip to content

[BUG] Desktop app: turns started by session resume or inter-session message hang with no first token, watchdog kills after ~980s (no_response) — spiked after auto-update 1.26832.0 → 1.28929.0 #86344

Description

@jinipack-droid

Environment

  • macOS (Darwin 25.5.0), Claude Desktop app 1.28929.0 (auto-updated from 1.26832.0 on Aug 11–12), bundled Claude Code engine 2.1.227, standalone CLI 2.1.220
  • Many concurrent sessions in the desktop app, heavy use of inter-session messaging

Symptom
Turns that are started without a human typing in the focused window — i.e. (a) turns triggered by an inter-session message, or (b) turns that require waking a session previously paused by idle timeout — intermittently produce no first token at all. The spinner runs until the health watchdog logs:

[CCD CycleHealth] unhealthy cycle for local_… (980s, hadFirstResponse=false, reason=no_response)

Human-typed turns in a focused session almost always work. A Mac restart did not help; an app restart clears it only temporarily.

Measurements (from ~/Library/Logs/Claude/main.log, frozen copy, Asia/Seoul time)

  • 70 no_response rows across 29 sessions between Aug 12 11:16 and Aug 13 19:29. The three previous log files (Jul 30 – Aug 11, main1/2/3.log) contain 1 such row in total.
  • Session-resume correlation: of 34 Resuming session events in the affected window, 19 (56%) fall within ±60s of a stall start (stall start = watchdog time minus reported duration). The older logs contain 412 resume events with at most 1 no_response row total (upper bound ≈0.2%, measured with a coarser method). The pause/resume lifecycle itself existed in older versions.
  • 43 of the 70 stalls had an inter-session Sending message to session delivery within −60s/+180s of stall start. Total deliveries were ~220/day and most were processed fine — the failure is intermittent (confirmed lower bound ~10% of deliveries).
  • Example trace (one session, sanitized):
    11:02:43 [WarmLifecycle:session] Idle timeout reached, disconnecting local_…
    11:02:43 [CCD] Pausing session local_… (idle_timeout)
    11:07:31 Resuming session local_… / Starting local session / Loaded 137 transcript messages
    (no start-timing, no output)
    11:23:46 [CCD CycleHealth] unhealthy cycle for local_… (976s, hadFirstResponse=false, reason=no_response)
    
  • Stalled turns never log a [CCD start-timing] entry, so we cannot tell from the client side whether the query failed to start or the first response never arrived.

Confounders we could not separate

  • An account re-login happened on the same day as the auto-update.
  • The Aug 12 "degraded performance for multiple models" incident (status.claude.com) overlaps only 6 of the 70 stalls by start time; the rest are outside that window.

Workaround
Pressing Esc and re-typing the prompt as a human turn recovers the session. Re-sending the inter-session message does not.

Related
Similar but not identical to #40462 (that one reports mid-turn stalls with no timeout, revived by any new message; ours never starts the turn and is killed by the watchdog).

Happy to provide sanitized log excerpts on request.

Activity

  1. arthurmoraesfernandes-afk commented on Aug 14, 2026

    @arthurmoraesfernandes-afk

    This looks like the "wedge" mechanism documented in the follow-up comment on #86298, with app-log evidence: an injected cross-session message that the CLI holds (consent gate) gets drained into the CLI at a turn boundary and is never "echoed" back as a user turn; the app then logs isRunning held by unechoed input at result … pendingEchoUuids: […] and treats the session as busy indefinitely — phantom turn, watchdog kills, and the user's own typed prompts queue/stall behind a turn that never ends until a priority=next send (typing another message force-flushes) or a restart reclaims it. The no-first-token hangs on resume/inter-session turns match the wedged state.

  2. nicko-ai commented on Aug 16, 2026

    @nicko-ai

    Same failure class on macOS, engine 2.1.229 — with one piece of evidence I haven't seen in this thread yet: a thread sample of the stuck engine taken while the app claimed a turn was in flight. The engine was completely idle. The hang is in the desktop app's delivery/echo-tracking layer; the CLI process never receives the input.

    Environment

    • macOS Tahoe 26.5.2 (build 25F84, Darwin kernel 25.5.0), MacBook Pro M3 Pro, 18 GB
    • Claude for Mac 1.30096.5 (6e1346), bundled engine claude-code/2.1.229
    • Local session, ~200-message history (1.4 MB .jsonl), heavy inter-session send_message use
    • Reproduced 5+ times in one evening on one session (plus twice on two other sessions, same night)

    Direct evidence the engine never gets the message

    While the UI spinner had been running ~4 minutes and a send_message call returned "it will be processed after the in-flight turn finishes":

    • /usr/bin/sample <enginePid> — main thread parked in __CFRunLoopServiceMachPort → mach_msg2_trap, event-loop thread in kevent64, HTTP client thread idle in kevent64. Zero in-flight API requests. 1548/1548 samples identical.
    • ps: 0:05 CPU total over 4 min, 0 child processes.
    • Session transcript .jsonl mtime/size frozen since engine spawn (byte-identical).

    So: app says "turn in flight", engine is a parked event loop. The turn exists only in the app's state.

    Two distinct drop paths in main.log

    A healthy turn always logs, within ~10 s:

    LocalSessions.sendMessage: sessionId=local_… 
    Sending message to session local_…
    Mapping internal session local_… to CLI session <uuid>   ← the delivery receipt
    

    That Mapping line is present for all ~40 healthy turns that night and absent in every failure.

    Mode A — send races the previous turn's result (hit at 22:46, 23:06×2, 23:21×2, 00:36, 01:30):

    01:29:45 LocalSessions.sendMessage: … messageLength=87
    01:30:15 [Stop hook] Query completed for session local_…
    01:30:15 [LocalSessionManager] drained 1 deferred send(s) for local_…
    01:30:15 [LocalSessionManager] isRunning held by unechoed input at result for local_…
             { resultUuid: '…', pendingEchoUuids: [ '…' ], nextCycleUuid: null,
               hasPendingCycle: true, inputStreamHasPending: false }
    

    The deferred send is flushed into the closing cycle; its echo never arrives; isRunning is held indefinitely (observed 27+ min; CycleHealth only fires no_response after the engine is externally killed). Every subsequent user message queues behind the phantom turn.

    Mode B — send lands during engine respawn (hit at 01:58): app logs Resuming session / Loaded 204 transcript messages, the message renders in the UI as delivered, but no sendMessage → Mapping pair is ever logged and the transcript never grows. The UI and the transcript permanently diverge — the user believes the agent saw content it never received. This is nastier than the 2.1.228-fixed "Message sent but write failed" case because delivery is visually confirmed.

    Trigger profile

    Matches this issue and #86557 exactly: every failure was a turn not started by a human typing in the focused window — inter-session send_message (5×) or a send racing turn-end/idle-wake. Human-typed turns in the focused session never failed (40+/40).

    Workaround that recovers without data loss (except the queued messages)

    kill -TERM the specific claude … --resume=<cliSessionId> process (find it via ps | grep resume=<cliSessionId>; map UI session → cliSessionId via ~/Library/Application Support/Claude/claude-code-sessions/*/*/local_<id>.json). App logs process exited with code 143, releases isRunning, respawns on next typed message with history intact. Queued/dropped messages must be re-sent manually. Leaving the session unfocused for the 15-min idle pause also clears it — but any focus resets that timer, so in practice it never self-heals while the user is watching it.

    Suggested defect location

    LocalSessionManager echo tracking: when a drained deferred send's input-echo races the result event, pendingEchoUuids is never reconciled and there is no timeout — the log line isRunning held by unechoed input at result fires but nothing recovers. Separately, sendMessage during engine respawn is acked to the UI before the stdio stream to the new process exists, so the write is lost silently. An echo-timeout (echo not seen N seconds after result → replay the send or fail it visibly) would cover both modes.

    Update — after app restart on 1.30096.5, cross-session sends never arrive at all

    After a full app restart: 4/4 send_message deliveries to the same session were silently dropped — including into a warm, idle, just-verified engine (it had answered a human-typed "ping" seconds earlier). Each send returned success; none ever produced the Mapping receipt; the recipient .jsonl never contained the message. Human-typed turns in the same session remained 100% reliable throughout. On this build the feature is effectively 0% deliverable to an affected session, with false success reported to the sender every time.

  3. codebytere-ant commented on Aug 25, 2026

    @codebytere-ant

    duplicate of #86012

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:desktopbugSomething isn't workinghas reproHas detailed reproduction stepsplatform:macosIssue specifically occurs on macOS

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions