[BUG] Terminal mode: messages enqueued after first turn are never dispatched (CLI-side queue stall, no exthost restart) — v2.1.221, macOS 26.6
Summary
Two distinct silent-message-drop failure modes observed on the same machine on the same day, with measured log evidence for both. Mode A is a CLI-side queue stall in terminal mode (claudeCode.useTerminal: true): the first turn of a session completes normally, then every subsequent user message is enqueued and never dispatched — no API request, no error, no timeout, spinner state forever. Mode B is the known webview/exthost problem (#79760 / #79245): extension host SIGTERM restarts orphan running backends, and restored tabs silently drop messages.
Mode A is the new data point: it shows the enqueued-never-dispatched behavior of #82772 also occurs in terminal mode with no extension host restart involved, so the webview is not the only trigger surface.
Environment
- Claude Code CLI: 2.1.221 (npm global install)
- VS Code extension: anthropic.claude-code-2.1.221-darwin-arm64 (pinned,
extensions.autoUpdate: off) - VS Code: 1.131.0 (unchanged since 2026-07-20, so not an app-update regression)
- macOS 26.6 (25G72), Apple Silicon (arm64)
- Node v24.12.0
- Config: 13 PreToolUse hooks, 15 MCP servers, ToolSearch active (~356 deferred tools)
Mode A — terminal-mode queue stall (new evidence)
Timeline from the session transcript JSONL (timestamps UTC):
11:00:55 session start (3 s after VS Code window load; VS Code had been
restarted twice within the previous 30 s)
11:00:59 user message #1 ("are you working?")
11:01:08 assistant reply ← first turn works, 9 s latency
11:01:13 system: stop_hook_summary + turn_duration ← turn closed cleanly
11:02:20 queue-operation enqueue ← user message #2 — NEVER dispatched
11:02:54 queue-operation enqueue ← user message #3 — NEVER dispatched
11:13:00 queue-operation enqueue ← user message #4 — NEVER dispatched
(no further entries; user gave up after ~17 minutes)
Facts measured for this window:
grep "reason 'killed'" main.logfor that VS Code window: 0 hits — no extension host restart involved.- No Claude webview extension logs in that window at all (terminal mode) — the stall is inside the CLI process.
- The three enqueued messages produced zero API requests and zero errors.
- After quitting VS Code and starting a fresh terminal-mode session (~6 min later), everything worked normally again — the very next session ran a long multi-turn workload without issues.
This matches the symptom description of #82772 (enqueued → never dispatched) but in a plain CLI/terminal context, with the turn state machine apparently stuck in "busy" after a cleanly-closed first turn (stop hook summary and turn_duration were both logged).
Mode B — exthost SIGTERM orphaning (same day, measured)
Earlier the same day, in webview mode, we measured the known orphaned-tab behavior:
- Extension host killed with
reason 'killed'in three separate windows (kills=1, kills=2, kills=1); in the last case the exthost was killed 9 seconds after session start. - Decision metric used:
grep -c "reason 'killed'" main.logandgrep -c "API REQUEST"in the extension output log. kills>0 or apiRequests=0 for a submitted prompt → tab is detached. - Key observation: the extension log went silent at 12:35:24 while the backend kept writing the session transcript until 12:39:39 — the backend survives, only the webview binding is lost, and messages typed into the restored tab are silently dropped (matches #79760).
- A clean uninstall/reinstall of the extension (VS Code fully closed) plus version pinning did not stop the SIGTERM restarts.
- Confirmed exthost-restart triggers on this machine: hot-swap extension reinstall while VS Code runs; built-in Copilot Chat (#81493 — disabling via
chat.disableAIFeatures: truehelped); settings.json changes that reload the extension.
Ruled out (with control experiment)
- CLI/extension version mismatch — both sides pinned 2.1.221; CLI 2.1.221 demonstrably healthy.
- Hooks / MCP / ToolSearch deadlock (#33073) — control: a CLI session with the same 13 PreToolUse hooks, 15 MCP servers, and ToolSearch active ran 18+ consecutive turns without a single stall.
- Binary integrity / AV — extension native binary signed
com.anthropic.claude-code, no quarantine attribute; no endpoint-security involvement demonstrated (note: the "Blocked by endpoint security" toast appears on any SIGKILL, see #58626, so it is not evidence of AV).
Why this report may be useful
Existing reports (#82772, #79760, #79245, #83771) describe the symptoms; this one adds (a) proof the queue stall occurs without the webview and without exthost restarts, (b) a concrete grep-based decision metric other users can run (reason 'killed' count + API REQUEST count), and (c) a control experiment excluding hooks/MCP as the cause. Happy to provide sanitized transcript JSONL excerpts or run additional diagnostics (--debug, --safe-mode) on request.
Workarounds currently in use
claudeCode.useTerminal: true(avoids Mode B entirely; terminal survives exthost restarts)- Never type into a restored webview tab; open a fresh tab and
/resume - On stall: Esc to flush the queue and retype; if dead, Ctrl+C and
claude --resume(no context loss — backend transcript is intact)
3 Comments
Update: controlled downgrade experiment, plus fix-side observations from the measured data
Downgrade experiment (running now)
We rolled both sides back to the last incident-free day's versions as a controlled experiment:
Fix-side observations, in case they help triage
claude --resumealso recovers with zero context loss. In the Mode A window the first turn closed cleanly (stop_hook_summaryandturn_durationboth logged), yet the dispatcher behaved as busy for the next 3 enqueues over 11 minutes. A watchdog for "message enqueued, no dispatch within N seconds, no turn active" that re-kicks the dispatcher — or at minimum surfaces an error — would turn a 17-minute silent failure into a sub-second retry.Data available on request
We hold substantially more local telemetry than what's visible in the public reports on this bug class: full session transcript JSONLs including the
queue-operationentries, per-window exthostmain.logkill counts, and extension output logs for every incident window that day — all preserved. Happy to share sanitized excerpts or run--debug/--safe-modereproductions on request.New incident — a third failure shape: extension host busy-loop freeze (GUI panel mode, extension + CLI v2.1.221, macOS)
Timeline (local time, all logs archived):
reason 'killed'entries inmain.logfor this window)[API REQUEST]dispatched, reply rendered normally ~7 s laterqueue-operationenqueue/dequeue OK; webviewtime_to_responseevent forturnIdx: 2logged[API REQUEST] /v1/messagesdispatched — the last line ever written to that logrenderer.log:Extension host (LocalProcess pid: 18936) is unresponsive.thenUNRESPONSIVE extension host: starting to profile NOW(the profiler's verdict never appeared in any log)What the process was doing: a macOS
sampleof the extension host shows the main thread 100 % inside JIT-compiled JS (no symbols), entered from a libuv fs completion callback —node::fs::FileHandle::CloseReq::Resolve()→ microtask drain → a tight JS loop that never yields. The last debug lines before the freeze wereFileHistory: Making snapshot for message <uuid>, so the timing is consistent with an fs-callback-triggered loop in the extension's file-history/snapshot path.Meanwhile the CLI backend was healthy the whole time: the session transcript JSONL shows both user turns arrived and the assistant's turn-2 reply was written at 15:50:22 — the exact moment the exthost was flagged unresponsive. No data loss; the UI just never rendered the reply. Same "backend keeps working while the UI is dead" signature as the earlier incidents in this issue, but a different mechanism: this time the API request did dispatch — the extension host itself froze.
Recovery that works:
Developer: Restart Extension Host— the renderer/UI stays alive (only extensions are dead), so the command palette is reachable.Possibly relevant environment, now removed as a controlled experiment:
chat.disableAIFeatureswas lost during a settings reset) — the #81493 triggerBoth are now disabled/uninstalled; I'll report back whether the freezes stop.
Two disclosures for anyone triaging:
extension.json this machine carries a small local patch todeserializeWebviewPanel(passing the persistedsessionIDinstead ofundefined, see #83322). That code path handles restored tabs only and was not exercised here — the failing session was fresh and no exthost restart preceded the freeze.Happy to share the full
sampleoutput, extension log, and transcript timestamps.Follow-up to my earlier comment on this issue, where I described a "confirmed" trigger for the extension-host restarts I was seeing. New data collected since then contradicts part of that comment, and I'd rather correct it than leave it standing.
What I said before, and why it needs correcting
I previously wrote that GitHub Copilot Chat was a confirmed trigger (citing #81493, and noting that setting
chat.disableAIFeatures: true"helped"), and separately attributed some Continue.dev startup config errors to "the #76633 family."Both need retracting:
exthost.log— not once, in any of the five. In the fifth event,chat.disableAIFeatures: truewas written to settings 1 second before that extension host started, and the host froze anyway 46 seconds later. My earlier "disabling helped" note looks like it was coincidence, not causation — I didn't have enough data at the time to tell the difference.claude --resumeon Windows — different mechanism, different platform, different author, and not actually about Continue.dev's startup config errors. That citation was a mistake; please disregard it.What the new data points to instead
Five extension-host freezes in one session, all 40–73 seconds after an extension host start:
| # | Host start | Freeze detected | Delay to freeze |
|---|---|---|---|
| 1 | 14:39:07 | 14:40:20 | 73s |
| 2 | 14:47:33 | 14:48:13 | 40s |
| 3 | 14:48:56 | 14:49:37 | 41s |
| 4 | 15:49:37 | 15:50:22 | 45s |
| 5 | 16:02:44 | 16:03:30 | 46s |
(Delay to freeze = time from this host's start to the "is unresponsive" report, not total time unresponsive — the host behind row 4 was still spinning at 100% CPU when sampled 8 minutes later.)
In all five, the Continue extension activated (
onStartupFinished) roughly 2 seconds after host start, and logged the same configuration errors every time (a YAML schema registration failure, plus an "Embeddings provider google-genai not found" error twice per startup). I uninstalled Continue at 16:03:01, mid-lifetime of host #5 — which still froze 29 seconds after the uninstall completed, consistent with "already active in the running process" rather than "present on disk." The next extension host started about 13 minutes later and has run 40+ minutes and counting under materially heavier load than any of the five freezing sessions (including sustained Claude Code activity), with zero freezes. Every one of the five freezes landed within 73 seconds of host start, so 40 minutes clean is well past the window where they showed up. That's still one clean run, not yet repeated.A macOS
sampleof one frozen host showed 100% of samples in a single JS callback chain throughnode::fs::FileHandle::CloseReq::Resolve, unsymbolized JIT frames throughout — consistent with a busy loop, not a deadlock, but it doesn't name Continue's code directly.I'm reporting the Continue-side evidence to that project directly rather than duplicating it in full here.
Where Claude Code fits (or doesn't)
Worth being explicit: this correction is largely exculpatory for Claude Code too. The local patch for #83322 (webview session-restore) was present for freezes #4 and #5 but absent for #1–#3, and all five froze the same way regardless of whether the patch was installed. The clean run that followed Continue's removal happened under heavier Claude Code load than any of the five freezing sessions, with zero freezes. Read together, presence of the Claude Code patch doesn't track with whether the freeze happens; Continue's presence does.
What I still can't prove
Apologies for the bad citation in the earlier comment — correcting the record now rather than leaving it while I keep digging.