[Regression, Windows+macOS] Cross-session messaging silently broken since CCD 2.1.224+ — messages dropped, recipient sessions wedged 15-20 min (hadFirstResponse=false); fixed in 2.1.237 for some installs, stable channel still affected
Environment
- Claude Code Desktop app:
1.28929.0.0(installed via Microsoft Store / MSIX) - Bundled CLI engine ("CCD"):
2.1.227(%APPDATA%\Claude\claude-code\2.1.227\claude.exe) — one version behind the latest2.1.228on the public changelog. Note:[CCD-autoupdate] Disabled: MSIX installin the app's own log — the bundled CLI cannot self-update on an MSIX install; it only advances when the Store ships a new Desktop package. - OS: Windows 11 (10.0.26200)
- Feature: the Desktop app's parallel-sessions panel, backed by remote MCP servers
ccd_session_mgmt/ccd_session/ccd_directory(confirmed via the app's own log as[CCD] [replaceRemoteMcpServers] Calling SDK with N total servers) — i.e.mcp__ccd_session_mgmt__send_messagefor messaging between open sessions,list_sessions/get_session/list_eventsfor status.
Summary
When session A sends a message to session B via send_message, B's next turn frequently produces zero output of any kind — not a single token, not a tool call, nothing — and stays that way until the Desktop app's own idle-timeout watchdog force-kills the query roughly 16–20 minutes later. In the UI this looks like B is "thinking" (spinner + elapsed timer) right up until it flips to a plain "Error" badge with no detail. This breaks the coordinator/worker pattern where several sessions relay results to a "manager" session via send_message.
Root cause, confirmed from the Desktop app's own log
%APPDATA%\Claude\logs\main.log contains the app's internal health monitor for each session's query ("CCD CycleHealth" / "WarmLifecycle"). Two independent affected sessions show the identical signature:
[warn] [CCD] Session <id> timed out after 975~997s of inactivity
(hadFirstResponse=false, last_message_type=user, last_tool_name=none, seconds_since_stderr=never)
[info] [CCD CycleHealth] unhealthy cycle for <id> (975~1227s, hadFirstResponse=false, reason=no_response)
[info] Session <id> query iterator completed
hadFirstResponse=false— the underlyingclaude.exeprocess never emitted a single token, thinking-delta, or tool call for the entire query.last_message_type=user— the last thing appended to the conversation before the hang was the injected<cross-session-message>(delivered as a user-role turn).seconds_since_stderr=never— the CLI process did not crash or write to stderr; it simply never responded to the query at all.- The app's watchdog only detects this after ~975–1227s (16–20 min) of total inactivity, then force-ends the query (
query iterator completed) — this is what surfaces as the "Error" badge; there is no earlier or more specific error surfaced to the user.
Clean within-session A/B (session local_08fe6fef, a fresh "test1" session)
The same session shows a healthy cycle immediately before the hang, isolating the trigger to the cross-session message itself rather than the session/model/effort combination:
13:05:52 [info] [CCD CycleHealth] healthy cycle for local_08fe6fef... (12s, hadFirstResponse=true) <- its own reply to a plain prompt
...
13:23:06 [warn] [CCD] Session local_08fe6fef... timed out after 975s of inactivity (hadFirstResponse=false, last_message_type=user, last_tool_name=none, seconds_since_stderr=never)
13:23:06 [info] [CCD CycleHealth] unhealthy cycle for local_08fe6fef... (975s, hadFirstResponse=false, reason=no_response)
Between these two cycles, the only thing that happened was: another session called send_message to deliver one short (<50 char) test message.
What we ruled out before finding the log evidence
- Context size / session age: the hung
test1session had only 2 prior turns and 9% context-window usage (88.2k / 967k tokens) at the time of the hang — ruled out by the in-app context panel. - Message complexity: the test message was a single trivial sentence.
- This specific session/model being generally slow: ruled out by the within-session A/B above — the same session answered normally 12 seconds earlier.
- Message delivery itself failing: the message content is visibly injected into the recipient's transcript before the hang begins (confirmed via
list_events) — delivery succeeds; it's the resulting query that never responds.
Reproduction count
5 independent hits on 2026-08-12: 3 freshly-created "worker" sessions crashed the first time a "manager" session dispatched an initial task to them via send_message (forcing the user to fall back to manually opening windows and pasting task text instead); the "manager" session itself later hit the identical hang receiving two worker reports; and the isolated test1 repro above.
Notable gap in status-reporting surface
While a session is in this stuck state, mcp__ccd_session_mgmt__get_session keeps reporting isRunning: true with a frozen lastActivityAt — there is no field visible through the session-management MCP tools that distinguishes "genuinely still reasoning" from "this query is dead." We could only tell by polling get_session twice ~60–90s apart and checking whether lastActivityAt had advanced, or (once we found it) by grepping the Desktop app's own log for CycleHealth/no_response. Surfacing hadFirstResponse/reason=no_response (or the elapsed-inactivity figure) through get_session would make this diagnosable without log access.
Distinct from a separate, already-understood behavior
Sending a message to a session where isRunning: false (fully idle, not mid-query) just queues into a mailbox — lastActivityAt updates to the send time but no query starts until the user manually opens that session's window. That's a different, milder issue than the one reported here, which occurs even when the recipient is actively running / has just been manually woken and picks the message up on its own.
Impact
Breaks the core multi-session coordination workflow (a "manager" session dispatching work to, and receiving reports from, several "worker" sessions via send_message), forcing a fallback to fully manual relaying between windows.
Possibly related (not duplicates — different subsystem/trigger)
- #85265 — async agent stall watchdog kills healthy long-running background subagents at a flat 600s (
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS). Same family (a watchdog force-ending a query that produced no stream chunk), but that one is about Task-tool subagents inside a single session, fires at 600s, and the author shows the killed work was actually still alive (resumable). Ours is the Desktop CCD session-level watchdog (975–1227s), triggered specifically by cross-session message injection, withseconds_since_stderr=neverand no independent evidence the query was making progress. - #84494 —
SendMessagetoCLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMSteammates has batching/delay/mis-routing defects. Different feature (in-process agent teams vs. Desktop's cross-window parallel sessions) but same broad area of cross-agent messaging reliability.
Questions
- Is this a known issue with the
ccd_session_mgmtremote MCP server / cross-session message injection path specifically (as opposed to the native single-machineSendMessagetool documented in the CLI changelog)? - What is actually happening inside the query during those 16–20 minutes of zero output — is it stuck before the first API call is even issued, or is a request in flight that never resolves?
- Could
hadFirstResponse/reason=no_response(already computed internally, per the log) be surfaced throughget_sessionso this is diagnosable without log access? - Is there a recommended workaround short of avoiding
send_message-based dispatch entirely?
Happy to provide full log excerpts / session IDs / more timestamps if useful.
41 Comments
Same regression here, same Desktop/CCD build, same signature. Adding what I think is missing from the thread: the version boundary and a before/after count from one continuous log, which should pin the introducing version.
The 2.1.222 → 2.1.227 boundary is visible inside a single
main.logThe MSIX auto-update landed while the app was running:
09:48:26 [info] [CCD] Initialized with version 2.1.222
09:48:27 [info] [updater] MSIX detected: ... exe=...\Claude_1.26832.0.0_x64__...\app\Claude.exe
09:51:06 [info] [CCD] Initialized with version 2.1.227
09:51:07 [info] [updater] MSIX detected: ... exe=...\Claude_1.28929.0.0_x64__...\app\claude.exe
Before/after, same log file (covers 2026-08-09 21:56 → 2026-08-12 14:36 KST)
| | before 09:51 (2.1.222) | after 09:51 (2.1.227) |
|---|---|---|
|
Sending message to sessiondispatches | 821 | 42 || healthy cycles (
hadFirstResponse=true) | 801 | — || no-response deaths (deduped) | 0 | 17 |
Independent confirmation from a second machine, same version boundary — and I pushed it back further using this machine's rotated
main*.loghistory, which happens to cover 5 consecutive CCD releases:| CCD version | Window (this machine) | Dispatches (
Sending message to session) |reason=no_responsedeaths ||---|---|---:|---:|
| 2.1.217 | 07-23 → 07-27 (~4d) | 1,290 | 0 |
| 2.1.217→2.1.219 | 07-27 → 08-02 (~6d) | 612 | 0 |
| 2.1.219 | 08-02 → 08-07 (~4.3d) | 1,515 | 0 |
| 2.1.221 | 08-07 → 08-09 (~2d) | 611 | 0 |
| 2.1.222 | 08-09 → 08-12 08:32 (~3.3d) | 1,128 | 0 |
| 2.1.227 | 08-12 08:32:13 → now | 45 | 16 (35.6%) |
Over 5,156 cross-session dispatches across 5 releases and ~18 days, zero deaths — then this machine's own MSIX auto-update landed (
[CCD] Initialized with version 2.1.222→2.1.227at08:32:13, same live-swap-while-running pattern @Williaman000 described), and the very next batch of dispatches started dying. Failure rate here (35.6%) is in the same range as theirs (~40%). First death on this machine was also within the first half hour after the swap.This lines up with everything in that comment: only cross-session recipients die, deaths cluster right after a dispatch batch, and the same-session-before/after control (this machine's coordinator sessions were fine on typed input right before and after the hangs). Two independent machines landing on the identical 2.1.222→2.1.227 boundary with a comparable ~35-40% failure rate is a pretty strong signal for where to start bisecting.
Also worth someone with access re-checking versions between 2.1.222 and 2.1.227 if telemetry/staged-rollout data exists — neither of us has a machine that sat on 2.1.223–2.1.226 long enough to narrow it further than "somewhere in this 5-version gap."
One more observation from this machine, flagged as a possible related mechanism rather than a confirmed one:
Several hours after some of these no_response deaths,
list_sessions/get_sessionreports the affected sessions asisRunning: false— butGet-CimInstance Win32_Process -Filter "Name='claude.exe'"still shows their underlying child processes alive, withCreationDatelining up against those sessions' earlier activity. Command lines are ordinary interactive sessions (--resume=<uuid>, no scheduled/background-task fingerprint), not something expected to linger.If the same code path that fails to produce a response after a cross-session message is also failing to clean up the process on abort/timeout, that seems worth checking together rather than as two unrelated bugs. It would also explain a separate complaint (mine and apparently not unique to me): Desktop app updates on MSIX installs sometimes only "take" after a full reboot —
[CCD-autoupdate] Disabled: MSIX installmeans the bundled CLI can only be replaced by the Store's file-level update, and Windows can't swap files still held open by orphanedclaude-code\<ver>\claude.exeprocesses, so the update gets deferred to next boot.Not asserting causation here, just noting the two symptoms (hung query, and process not torn down) show up on the same machine around the same failures, in case it's useful for narrowing down where in the abort path this goes wrong. Happy to share the
Get-CimInstanceoutput / specific PIDs if useful.Independent reproduction on macOS — same app + bundle versions, so this is not MSIX- or Windows-specific.
Environment
1.28929.0, bundled CCD engine2.1.227(~/Library/Application Support/Claude/claude-code/2.1.227/)mcp__ccd_session_mgmt__send_messageTimeline from
~/Library/Logs/Claude/main.logZero tokens, zero tool calls, zero transcript writes during the entire 1011s window (verified: no
*.jsonlunder~/.claude/projects/gained a single record, and the runner process sat at 0% CPU with no children beyond the usual MCP sidecars).Details that may help narrow it down
<cross-session-message from="local_…" name="…" encoded="1">) andlist_eventsshows it, but its text is not findable in any transcript.jsonlon disk — the recipient's transcript gained zero records from delivery through the reap.send_messagereturned "Message sent to session …", and nothing was ever reported back when the recipient's cycle was reaped 17 minutes later. During the whole window the recipient showedisRunning: trueinlist_sessions, which makes the zombie indistinguishable from real work for any coordinator/orchestration pattern — we only caught it because we cross-checked CycleHealth in main.log.Happy to provide fuller logs on request.
Windows regression window confirmed by rollback: works on Desktop
1.25927.0(CCD engine2.1.221) → broken on1.28929.0We rolled back and ran a controlled bidirectional test — the bug disappears entirely.
Environment
1.25927.0(released 2026-08-04, manually rolled back via the official installer), CCD engine2.1.2211.28929.0, where we hit the zombie on 4+ independent recipient sessions the same morning (exact signature from this issue:hadFirstResponse=false, reaped at 975–1227s withreason=no_response,seconds_since_stderr=never, sender sees success, recipient showsisRunning: truethroughout)Bidirectional test on 1.25927.0 (2026-08-12,
%APPDATA%\Claude\logs\main.log)Session A sent a test message to two idle sessions (both
isRunning: false), asking each to reply viasend_message:Both idle recipients woke on delivery, processed the message in 14–16s, and replied; the original sender then received and processed both replies normally — the exact wake-on-message path that zombied on
1.28929.0. Since the rollback restart, main.log shows every cycle healthy and zerounhealthy/timed out/SESSION ERRORentries.Possibly the same regression, second symptom
On
1.28929.0we also repeatedly observed a milder failure mode:send_messageto an idle (isRunning: false) session updates itslastActivityAtbut never starts its process — the message sits unread until the user manually focuses that window (3/3 fresh sessions, waits of 19–60 min with zero activity, zero process spawns). On1.25927.0, wake-on-message works (see above). So "doesn't wake idle sessions" and "wakes but zombies withhadFirstResponse=false" may be two symptoms of the same delivery-path regression between these versions.Warning for anyone using rollback as a workaround
Squirrel auto-update silently re-downloads
1.28929.0and re-rigs the execution stub within a minute of first launching the rolled-back build (Squirrel-Update.log:Squirrel Enabled Apps: [...app-1.28929.0\claude.exe]). The next app restart silently puts you back on the broken version — the rollback only holds for the current process lifetime unless auto-update is blocked.Official installer for the last known-good version (from claude.ai downloads): https://downloads.claude.ai/releases/win32/x64/1.25927.0/Claude-003700efafbc2ccb4b1177a5e637b14da381799e.exe
Another Windows data point, with app-side log evidence, exact send-to-onset arithmetic on three separate cases, and one instance where I know who sent the message because I sent it myself.
Environment
%APPDATA%\Claude\claude-code\2.1.227\claude.exe(only version present; the app picks it at startup)Not reliably reproducible on demand. I cannot give steps that trigger it. What I can give is a rate: nine watchdog events in 8.4 hours on one machine under normal multi-session use, three of them within 90 minutes. It is frequent enough to catch with a log tail, and it names three of six sessions with zero events on the other three.
The nine events
The onset is the send, to the second
The cycle duration equals the reported inactivity in each case, so the cycle began at the send. Subtracting gives the send timestamp exactly:
This is not a session degrading over time. SESSION_A was answering normally 52 seconds before the message that silenced it:
A working session takes one specific message and goes permanently silent on it.
The 07:42:01 send was mine, and it was ordinary
The third case above is the one I can speak to directly: that send was an MCP cross-session
send_messageissued by another local session on the same machine, carrying a short plain-text status note. Benign content, no attachments, recipient idle at the time. It stalled the recipient for 992 seconds. The same recipient took 18 other sends across the same log without stalling, so the trigger is intermittent rather than deterministic.A partial reap, and the escape hatch that saves the sessions that survive
This is the part I have not seen mentioned in this issue, and it may be the most actionable. The unaffected sessions are not a healthy population. They hit the same condition and escape it by one of two routes. Caught live on a session that did not hang:
The reaper ran and left one uuid behind:
remainingCount: 1, not 0. Fifteen minutes later the next send to that session would have been deferred behind the still-heldisRunning, and it was not, only becausesend bypassed deferfired. Note the condition on that bypass: CLI at turn boundary. A session sitting mid-turn when the send arrives has neither a completed reap nor the bypass, and I believe that is the population that hangs.Corroborating, on two of the earlier stalls the held echo is never reaped at all, and the cycle-duration arithmetic points at the hold rather than at the user message:
Against my own hypothesis, and stated plainly: the 07:33 and 07:35 stalls had no outstanding held echo at onset. SESSION_A's queue had been reaped to
remainingCount: 0at 07:14:39. So a held echo is sufficient in some cases but is not necessary, and something else reaches the same end state.User-visible impact: the message is silently lost
The UI surfaces this as an orange warning triangle on the session, tooltip "Claude Code stopped responding", and an in-session banner reading "Claude Code stopped responding / Try sending your message again". That wording is accurate and worth acting on: the message is not retried, and in at least one instance I confirmed it produced zero records in that session's CLI transcript, so it never reached the CLI at all and leaves no artifact on either side. From the user's perspective a worker session sits idle for roughly 17 minutes looking busy, then quietly resumes as though nothing was sent.
Other observations that may narrow it
query iterator completedlands at the watchdog, not at stall onset, so the child's output stream stayed open through the entire silence.seconds_since_stderr=neverin all nine events. That field has never emitted a numeric value anywhere in five log files on this machine, so I would not read it as evidence by itself.CLI dropped the isReplay echoappears about 3 times across 17 days of pre-update logs and 18 times in 8 post-update hours. It exists in both builds, so unlikereason=no_responseit is a valid cross-build comparison, and the rate change is large.send bypassed deferis new in 1.28929.0 and fired only on sessions that did not hang.LocalSessions.sendMessageline, i.e. the cross-session/MCP shape. One was a normal UI send, so the UI path is not immune.--resume, but only later and only on user focus, 14s to 7m31s after the give-up.--resumeis the recovery, not a crash marker.claude.exe. Note this is narrow: WER records unhandled-exception faults only andCrashDumpsis not armed for the binary here, so process death in general is not excluded, only an OS-recorded crash.session limit/usage limit/rate limittext and zeroreason=api_errorin the current log, against 44 to 73 limit hits and 52 to 93 api_error hits in each of the four pre-update logs on the same machine. The limit code path is well exercised here and none of it fired.Session identifiers above are pseudonyms, consistent within this comment. Happy to supply further pseudonymized line families from
main.logon request; I cannot share the raw log, as it embeds absolute paths and private repository names.Knowing when the fix lands
Auto-update applies within roughly four hours of publication, so no action is needed — but if you want to watch for it, anything above 1.28929.0 here is the new build:
curl -s https://downloads.claude.ai/releases/win32/x64/RELEASESRoot cause: delivery succeeds — the recipient CLI discards the message before it is ever enqueued, and never tells Desktop.
We chased this end-to-end on Windows and can now describe the mechanism. Short version: the send side is fine. The message does reach the recipient CLI. The recipient then classifies it as the new native cross-session peer message; that feature is disabled on Windows, so it refuses and drops the message before anything is enqueued. Because the refusal is never surfaced back to Desktop, Desktop keeps waiting on a query that will never produce a token — until the watchdog gives up 975–1227s later with
hadFirstResponse=false, reason=no_response.The recipient CLI says so itself, in its own debug output:
dropped before attachment materializationaccounts for every missing signal in the watchdog line: no prompt enqueued, no turn started, no transcript append, no/v1/messages, no stdout, no stderr. It also explains the two other symptoms in this thread — the message being visible in the app's conversation store but never appearing in the recipient's CLI transcript, and idle recipients that appear never to wake up.Version boundary. Feeding the same Desktop-shaped input to two official CLI builds, in an isolated offline environment (separate config dir, endpoint pointed at a closed local port, so no production traffic and no quota used):
2.1.221— accepted; reaches[API REQUEST] /v1/messages source=sdk2.1.227— refused as above; zero API requests, 0 bytes stdout, exits 0 in ~1.5sThat matches the app-level boundary several of us independently reported: Desktop
1.25927.0works,1.28929.0does not.User settings don't help. The inbound setting appears to be consulted after the platform availability check, so on Windows it can't override it. Verified with the setting applied and the app fully restarted — still hangs.
Scope note. The kill-switch branch above is Windows. The macOS reproduction in this thread may take a different branch of the same gate (e.g. held rather than refused); we haven't been able to confirm that directly.
What we think needs to change, in priority order:
ccd_session_mgmtinjection as a native peer message. It's a separate, pre-existing transport that worked fine before the native feature landed, and the Windows kill switch for the native feature shouldn't apply to it.continue. Today the failure is indistinguishable from "the model is thinking" for 16–20 minutes, and the message is then silently lost with no retry and no artifact on either side.send_messagewould distinguish "written to stdin" / "accepted by recipient" / "recipient started a turn", so the sender doesn't get a false success while the recipient is already dead.As far as we can tell there is nothing we can do about this from our side — the drop happens inside the recipient CLI, before anything we control — so we're hoping it can be addressed in an upcoming release. Happy to provide more detail if it would help.
WORKING User Side Fix - MacOS
Claude fixing claude is making my head hurt... This is claude's findings. confirmed by me.
macOS takes a different branch than the Windows kill switch — and on macOS there is a working user-side fix:
"crossSessionInbound": "accept".@WarmBed's scope note asked whether the macOS reproduction takes a different branch of the same gate. It does. Verified on macOS by reading the gate in the shipped 2.1.227 binary and then confirming the fix end-to-end with a controlled test.
Environment
1.28929.0, bundled CCD engine2.1.227(~/Library/Application Support/Claude/claude-code/2.1.227/)bypassPermissions.send_messagesends dropped before the fix; 1/1 delivered after.The macOS branch is
hold, notrefuseThe receiver-side gate (
WXpin the 2.1.227 binary) ends with:yva/gvamapbypassPermissions(and plan-with-bypass-available) to"bypass". So a receiver in bypass mode holds any inbound cross-session message whose sender did not attest its permission mode. The binary's own user-facing string for this cause:This is directly observable in the two message wrappers. The native peer channel attests; the Desktop
ccd_session_mgmtinjection does not:| Channel | Wrapper | Gate result |
|---|---|---|
| native
SendMessage(UDS) |from="uds:/tmp/cc-socks/…" from-name="Test1" from-mode="bypass"| accepted ||
ccd_session_mgmt send_message|from="local_561c4b1d-…" name="Test1" encoded="1"— nofrom-mode| held |That explains the otherwise confusing result that on the same machine, same core, same target session, one channel works and the other silently drops.
Held messages are parked in a buffer (
KXpcase"hold"), evicted oldest-first when it fills, and settled asexpiredon shutdown — never retried, no artifact on either side. Confirmed here: messages held before the fix were not released or replayed afterwards, they were simply gone.Windows differs because
DS()short-circuits first:zXp()turns thatfalseinto{policy:"refuse", refuseCause:"kill-switch"}before the settings lookup — which is exactly why @WarmBed found user settings can't override it on Windows. On macOSDS()is true (demonstrated by the native peer channel working), so the setting is consulted and wins.The fix (macOS)
Valid values are
accept|hold|refuse. InWXpthe explicit setting is read immediately after the kill-switch check and before all mode logic, so it overrides theno-mode-assertedhold.Verified end-to-end. Same target session, before and after, watching the target's transcript for the
queue-operation/enqueuerecord that marks real delivery:| | Before setting | After setting |
|---|---|---|
|
ccd send_message| 3/3 dropped, no enqueue record ever | delivered,enqueueat 03:34:22.022Z || Reply from target | none | token echoed within ~15s |
It took effect without restarting the app. Note the precedence rules in
qXp():policySettings/flagSettings/userSettingsare checked first, thenlocalSettings/projectSettingsmay only tighten (Loi={accept:0,hold:1,refuse:2}), so a repo setting ofholdwill override a useraccept.Diagnosing this without log access
A real delivery always writes a
queue-operation/enqueuepair to the recipient's transcript at~/.claude/projects/<encoded-cwd>/<uuid>.jsonl, followed by auserturn. A held message writes nothing at all.get_sessionandlist_eventsare not reliable here — they read the app's session store, so they will happily showisRunning: trueand list a message the CLI never received. The transcript is the only ground truth.What this suggests about the fix
The hold itself looks intentional — it's a security gate, and holding an unattested message aimed at a session that bypasses permission prompts is defensible. Two things seem wrong regardless of that:
from-mode, so under the current gate it can never satisfy a bypass-mode receiver, and the default configuration makes that the common case. Either the coordinator/task-notificationorigin should be exempt (it is a pre-existing transport, distinct from the native peer feature), or Desktop should stamp the sending session's mode.KXpcallsonPeerHeldwith a review dialog payload (dialogBody,preview,claimedName,peerPid), and the binary contains the string "It is being reviewed before delivery." — but no dialog, banner, or notification ever appeared in Desktop on this machine across 3 held messages. If that review UI surfaced, this would be a visible "approve this message?" prompt rather than a 16–20 minute silent hang. That may be the same defect as #85714.Also,
send_messagereturning success for a message the recipient held or refused is what makes this undetectable from the sending side — agreed with @WarmBed's point 3 that the transport should distinguish written / accepted / turn-started.Minor triage note
#86014 is the same regression, labeled
platform:windowsonly but confirmed on macOS there and here; it also postdates this issue by ~7 minutes, so it is probably the duplicate.Windows confirmation with two additional data points not yet in this thread. (Our report #86088 is labeled a probable duplicate of this issue; consolidated evidence here.)
Environment: Windows 11 Pro 10.0.26200, Desktop
1.28929.0, engine auto-updated 2.1.222 → 2.1.227 on 2026-08-12 ~05:55 UTC. Before: 103 session spawns over 3 days, zero failures, heavy daily cross-sessionsend_messageuse. After: 6/6 inbound cross-session deliveries to idle sessions hung with zero recipient output and were watchdog-killed at 966–1010s (hadFirstResponse=false, last_message_type=user, last_tool_name=none, seconds_since_stderr=never). Timeline and mechanism match @WarmBed's kill-switch analysis exactly.New observation 1 — a stuck inbound message re-bricks the session after recovery. A session that had a pending (refused/hung, then watchdog-killed) inbound message was later focused by the user, who typed a short nudge. That user-typed turn ran normally (healthy cycle, tool calls, visible output). ~3 minutes later — with no further user input — a new cycle began with
last_message_type=user, last_tool_name=none, produced nothing, and was killed at 991s. This is consistent with the still-pending peer message re-attempting delivery and being refused again: one undeliverable message serially kills an otherwise healthy session. The pending message is never written to the recipient's transcript.jsonl, so it can neither be processed nor cleared from the recipient side.New observation 2 — delivery into an actively-running focused session succeeds on 2.1.227. Same day, same sender tool: a cross-session message delivered while the recipient session happened to be mid-conversation (user actively focused in its window) ran a healthy 218s cycle. Deliveries to idle/background sessions: 6/6 killed. This supports the two-transport theory — live sessions appear to receive the message via the pre-existing injection path, while idle-session wakes route through the gated native peer path, which
DS()kill-switches on Windows beforecrossSessionInboundis consulted (so no user-side override exists on Windows, as @WarmBed verified).Impact: all multi-session coordination on Windows is dead since the 2.1.227 rollout; the only workaround is the user manually babysitting each recipient session, and per observation 1 even that can be undone by the pending message re-delivering.
Asks:
ccd_session_mgmtinjection from the native-feature kill switch (it is a distinct, pre-existing transport), or have Desktop stamp the sender's mode so the gate can evaluate it.stabledist-tag is still2.1.222while Desktop bundles2.1.227.send_messagedistinguish written / accepted-by-recipient / turn-started so senders stop getting false success, and clear or surface pending-but-refused messages instead of retrying them into every future cycle.@007ArunSharma Observation 2 matches what we see, but our data narrows the boundary in a way that matters for anyone trying to use it as a workaround:
isRunning: trueis not sufficient — the recipient has to be genuinely mid-turn.Our clearest case, Windows, Desktop
1.28929.0/ engine2.1.227:That session was warm and
get_sessionreportedisRunning: truethroughout — it had finished a turn 6 minutes earlier and was sitting inside its 900s idle window, not yet paused. It still died. So the passing case in your observation 2 is specifically actively mid-turn, not merely "the process is alive" or "the app shows it as running". The warm-but-between-turns state behaves like idle.That distinction is easy to get wrong from the outside, because the two states are indistinguishable through the tools a sender has:
get_sessionreturnsisRunning: truefor both, andlist_eventswill happily show the message in the app's conversation store in both cases. As @nutballs noted, the recipient's transcript is the only ground truth — a real delivery writesqueue-operation/enqueueto~/.claude/projects/<encoded-cwd>/<uuid>.jsonl, a gated one writes nothing.Two smaller points from our side:
On your observation 1 (pending message re-killing the session). We saw the same shape from the other end. Three cross-session messages were sent to one test session across ~20 minutes; its transcript stayed byte-identical the whole time, and on the next app restart the engine logged
Loaded 2 transcript messages— the two turns that predated the first cross-session message. None of the three ever became a transcript record, so there is nothing on the recipient side that could acknowledge or discard them. That is consistent with a pending item that can only be retried, never settled.On the settings override. We tested
"crossSessionInbound": "accept"on Windows twice — once with the engine respawned after the setting was written, and once after a full Desktop quit and relaunch — and it changed nothing, which lines up with @nutballs' reading thatDS()short-circuits before the setting is consulted on this platform. Worth stating explicitly so Windows users don't spend time on it: the macOS fix in that comment does not port.Re your ask 2 (point Desktop back to the stable engine): that matches what we found by rollback — Desktop
1.25927.0(engine2.1.221) is completely clean here, 1,128 cross-session dispatches over the preceding two days with zero watchdog events, versus 16/45 killed in the first hours after the update.Status update from the Windows reporter above, plus a retraction of my own earlier hypothesis and a methodological warning for anyone rolling back.
1. Retraction: my echo/defer mechanism was wrong
In my earlier comment I proposed that the stall came from the app's input-echo/defer bookkeeping — a
pendingEchoUuidleft unreaped (remainingCount: 1) holdingisRunning, withsend bypassed deferas the escape that saved unaffected sessions. It matched two stalls to the second, and it is not the cause. @WarmBed's kill-switch analysis is.The cleanest disproof is from my own rollback window:
isRunning held by unechoed inputfired 100 times in 24 hours with zero stalls. A condition that occurs a hundred times without ever producing the failure is not causal. Please disregard that part of my earlier comment; it will only send triage sideways.2. Rollback status: 24 hours clean, with delivery positively confirmed
Rolled back to Desktop
1.25927.0/ engine2.1.221, blocking auto-update. Same machine, same six-session multi-session workload.| | before:
1.28929.0/2.1.227| after:1.25927.0/2.1.221||---|---|---|
| window | 15.5 h | 24.0 h (08-12 10:24:19 to 08-13 10:23:06) |
|
Sending message to session| 122 | 509 || cross-session messages verified delivered | — | 420, across 21 recipient transcripts |
|
healthy cycle for| 75 | 424 ||
unhealthy cycle for(any reason) | 9 | 0 ||
reason=no_response| 9 | 0 (see section 3 — not meaningful) ||
dropped the isReplay echo| 20 | 0 ||
isRunning held by unechoed input| 22 | 100 |The 420 is measured with @nutballs' ground-truth method rather than from app logs: a genuine delivery appears in the recipient's
~/.claude/projects/<encoded-cwd>/<uuid>.jsonlas auserrecord whose content is the<cross-session-message from=…>wrapper. I parsed the JSONL and counted only those, explicitly excluding 8 occurrences that appear insidetool_resultblocks — those are one session quoting another session's transcript, and a naive substring grep counts them as deliveries. That inflated my first pass from 420 to 950, so it is worth flagging for anyone else measuring this way.This corroborates @WarmBed's rollback result (1,128 dispatches clean) on an independent machine.
3. Warning: rolling back removes the detector, so a clean log is not evidence
This matters for the several people in this thread now rolling back and reporting all-clear.
reason=no_responseand[CCD] … timed out after Ns of inactivityare new code in 1.28929.0. Measured across my four rotated pre-update logs — covering1.24012.9,1.24012.11,1.25927.0and1.26832.0—reason=no_responseoccurs 0 times, and the only reason values that ever appear historically areapi_error(60) andapp_quit(2). On a rolled-back build that string cannot be emitted, so its absence carries no information whatsoever.What does survive the rollback, and is therefore a valid cross-build instrument:
[CCD CycleHealth]itself predates 1.28929.0 and is live on the older builds — it appears 1,114 to 2,706 times per rotated log, andunhealthy cycle forgenuinely fires there (10, 12, 20 and 20 times respectively, including in the log covering1.25927.0). Sounhealthy cycle for= 0 after rollback is a live instrument reporting zero, not a missing one. That is the number to quote.If you roll back and report "no more
no_response", you have measured nothing. Quoteunhealthy cyclecounts and delivered-message counts instead.4. Fix status, as of 2026-08-13 10:20 UTC-5
For anyone waiting rather than rolling back:
1.28929.0exists. The Squirrel feed atdownloads.claude.ai/releases/win32/x64/RELEASESstill lists onlyAnthropicClaude-1.28929.0-full.nupkg, and the enterprise MSIXlatestredirect still resolves to1.28929.0.latestis now2.1.231,stableis2.1.223. I read the changelogs for2.1.228,2.1.229and2.1.231in full: none mentions the cross-session inbound gate, the kill switch, or the legacyccd_session_mgmtinjection.2.1.229does carry Windows fixes, but for extended-length/UNC paths and the scheduled-tasks watcher.1.28929.0pins2.1.227, and no engine that would fix it has shipped yet in any case.Echoing @007ArunSharma's ask 2: pointing Desktop's pinned engine back to a pre-
2.1.224build would resolve this for every Windows user without waiting on the classification fix, since the native peer feature that the kill switch guards did not exist before then.Desktop
1.30096.1shipped, and it does not fix this. It pins engine2.1.229.Flagging early because the update is being offered now and the "Relaunch to update" prompt reads like a fix has landed.
What the new build actually carries
Read out of the new package's own
resources/app.asar(%LOCALAPPDATA%\AnthropicClaude\app-1.30096.1, downloaded and fully extracted here at 2026-08-13 18:54 local, 2400 files / 561 MB):Four things follow, and I want to be clear which are measured and which are not:
2.1.229is inside the affected2.1.224+range.2.1.229's entire changelog is one entry, about documentingclaude remote-control --continue. Nothing touches the inbound gate. Same for2.1.228and2.1.231, which I read in full earlier in this thread.2026-08-12T17:59:47Z— roughly 15 hours before @WarmBed posted the binary-level kill-switch analysis on 08-13 at 08:54Z. It could not have been built in response to a root cause that was not yet public.2.1.229still refuses. I do not have that binary; only2.1.221is on this machine.A cheap discriminator, if someone has 2.1.229 handy
I validated a string test against the version @WarmBed measured as accepting, with both controls, so it discriminates rather than merely matching everywhere:
| grep over engine
2.1.221(accepting) | matches ||---|---|
|
cross-session-inbound| 0 ||
cross-session messaging disabled| 0 || any other
cross-session*string (positive control) | 15+ |The gate's own debug string is absent from the accepting build and reported present in
2.1.227. So on any2.1.229install,rg -a -c 'cross-session-inbound' <engine>/claude.exereturning non-zero is good evidence the gate shipped again. Someone who has taken1.30096.1can settle in one command what I can only infer.On the title
@WarmBed — would you consider retitling? The current title is accurate but front-loads the telemetry fields, and I think it undersells the impact enough to affect triage priority. The thing that actually happens to users is that a running session is killed and its in-flight work is lost. Something like:
Entirely your call, it's your issue — just flagging that several of us have now independently lost session work to this and the title doesn't convey that.
Reproduced on the latest versions: desktop app 1.30096.0.0, bundled runtime 2.1.229, Windows 11 (10.0.26200), after a clean reboot.
What changed vs 2.1.227: the message now actually arrives and renders in the recipient session (the 2.1.228 delivery-failure fix appears to work — sender gets success and the message is visible as '총괄의 메시지' / cross-session-message block in the recipient transcript).
What did NOT change: immediately after the message is injected, the recipient session's turn produces zero output — spinner running 1m31s+ with no token, no tool call, exactly the hadFirstResponse=false hang described here. Screenshot-confirmed by the human operator watching the recipient session.
So on 2.1.229 the failure mode shifted from 'silent non-delivery' to 'delivered, then recipient hangs'. Happy to provide logs.
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 apriority=nextsend (typing another message force-flushes) or a restart reclaims it. hadFirstResponse=false cycles are the app-side signature of this state.@wshallwshall -- here is the
2.1.229measurement you asked for. I have1.30096.1installed, so I ran your discriminator directly. The gate did ship again, and on every gate string I tested2.1.229returns the same counts as2.1.227. (String counts only -- I did not diff the binaries themselves.)1. Your discriminator, run on
2.1.229Windows 11 10.0.26200, Claude Desktop
1.30096.1.0(MSIX / Microsoft Store install), bundled engine2.1.229at%APPDATA%\Claude\claude-code\2.1.229\claude.exe.2.1.227is still on disk, so both are measured on the same machine with the same command.rg -a -c <string> <engine>\claude.exe(lines containing the string):| string |
2.1.221(yours, accepting) |2.1.227(mine) |2.1.229(mine) ||---|---|---|---|
|
cross-session-inbound| 0 | 10 | 10 ||
cross-session messaging disabled| 0 | 2 | 2 ||
cross-session(positive control) | 15+ | 45 | 45 |Cross-checked by match count (
grep -ao ... | wc -l) rather than line count, in case of packing artifacts:cross-session-inbound27 vs 27,cross-session messaging disabled3 vs 3. Two further gate strings are also identical across the two builds:kill switch8 vs 8,refused inbound peer message2 vs 2.So your INFERRED line can be marked MEASURED:
2.1.229still carries the gate, with counts identical to2.1.227on every string tested. Nothing in this build touched that code path.Caveat on my side: I do not have an accepting build on disk (
2.1.222was reaped by the installer), so I cannot reproduce your negative control locally -- I am relying on your2.1.221baseline for discrimination. My positive control (cross-session= 45) at least confirms the search reaches strings in this binary.2. Behavioural retest on
1.30096.1/2.1.229, sender and recipient both on the new buildIndependent confirmation of @Heeyoung-Ahn's and @arthurmoraesfernandes-afk's retests, with one difference noted below.
Method: unique-token probe, verdict taken only from the recipient's CLI transcript JSONL, never from
list_eventsor the rendered card.AI_AGENT=claude-code_2-1-229_agent).send_messagereturnedMessage sent to session ....Resuming session ...->Loaded 209 transcript messagesatT+0s, then[WarmLifecycle:session] Starting idle timeout ... 900s13 seconds later -- i.e. it woke and went idle without ever starting a turn.Difference worth flagging: @Heeyoung-Ahn reported that on
2.1.229the message now does reach the recipient transcript and the hang moves to the turn. On this machine it still does not reach the transcript at all -- the older2.1.227shape. Same app build, so there may be a second variable (MSIX vs Squirrel install, or recipient warm/cold state).3. Two smaller confirmations
crossSessionInbound: "accept"is not a workaround here either -- consistent with @arthurmoraesfernandes-afk and @Bianco24siete. Given the string counts above are identical to2.1.227, this is expected rather than surprising.[CCD-autoupdate] Disabled: MSIX installinmain.log-- and the Store does not offer downgrade. The1.25927.0installer route several of you used is not reachable from an MSIX install, so for Store users there is currently no workaround at all, only avoidance.4. Two verification traps that cost me a day, in case they save someone else
Both produced confident but wrong conclusions before I caught them:
list_events/get_session/ the rendered card are not delivery evidence. I used them to "confirm" delivery after a restart and reported the bug fixed; it was not. Re-auditing with transcriptenqueuerecords showed 0 cross-session deliveries in that window -- every delivery I had "verified" was app-store-only. This matches @nutballs' and @WarmBed's point; I am adding it because I made exactly the mistake they warned about, one day later.Also, on this machine the app log's watchdog signature is a clean discriminator:
timed out after ...s of inactivity (hadFirstResponse=false ...)occurred 4 times, all after the engine swap, 0 times before it, over the same workload.Happy to run any further one-command checks on
2.1.229/ MSIX -- the binary is here.Delivery and turn execution are separable: payload reaches the recipient's transcript, then the turn produces zero tokens
Windows 11 Pro 10.0.26200, desktop app, bundled CLI 2.1.229, native Windows, no WSL. Sender and recipient are both local interactive sessions.
crossSessionInboundanddialogExpirywere unset (defaults) for this test. Sender was in auto mode, so it identifies as prompting rather than bypassing.Recipient: opus-5, xhigh effort, created three minutes before the test, idle (
isRunning: false) at send time.Method. Send a tokened probe via
send_message, then read the recipient's own transcript withlist_eventsrather than relying on the UI card, and pollget_sessionforisRunning/lastActivityAt.Result.
| Step | Observed |
|---|---|
| Send receipt |
Message sent to session … ("…")|| Recipient transcript | Message present as a
[user]event, wrapped in<cross-session-message from="local_71a…" name="…" encoded="1">|| Wake |
isRunningflippedfalse→true||
lastActivityAt| Froze at the arrival timestamp and did not advance for 3+ minutes || Assistant output | None. No tokens, no
[result]record || Hold notice / approval dialog | Neither, on either side |
Second send, testing the force-flush claim. A follow-up send ~3.5 minutes later returned different wording:
That message also landed in the transcript as a
[user]event, andlastActivityAtadvanced to its arrival time. Still zero assistant output. Since the in-flight turn never finishes, a queued follow-up is never processed. A second send does not unwedge the session.Why this may help narrow it. The payload is not lost in the app layer for this instance, and it is not held-then-expired: it is written to the recipient's transcript as a user turn within seconds, with no dialog and no hold notice. The wake path works, and message arrival updates
lastActivityAt. What fails is strictly the turn, which starts and emits nothing.One limit on that claim: transcript presence is not proof the model received the text. Because the turn emitted zero tokens, whether it entered the model's context is indeterminate from outside. What is ruled out is loss before the transcript.
This suggests reports describing "never written to the transcript" and reports describing "delivered then hangs" may be two distinct failures being merged, and that on 2.1.229 the second is what reproduces.
The gate is on the SENDER side, not the recipient — and that is why none of the recipient-side workarounds in this thread work.
Windows 11 10.0.26200, Desktop
1.30096.1(MSIX / Microsoft Store), bundled engine2.1.229. Broken here since2026-08-12T02:40:42Z(last real delivery), measured against 1,945 historical inbound deliveries. Yesterday: 9 probes to 4 sessions, zero delivered.Today it works again, both pipes, 9-second delivery, round trip. What changed was on the sending side only.
---
The measurement that isolates it
The decisive test was sending to a session that does not have the fix:
A recipient with the gate closed receives fine. What was broken was the outbound path.
That inverts the working theory in this thread. The reports here — including mine in #86237 and #86298 — describe it as "the recipient holds it", "the recipient's consent gate refuses it", "the recipient never receives it". Every remedy tried against that reading failed, and now there is a reason: they all target the recipient.
crossSessionInbound: "accept", recipientpermissionMode: "default", waiting for the 2.1.228 inbox fix, restarting the recipient — all recipient-side, all negative (I tested each).Both transports recover together, which is consistent with a single shared gate rather than two bugs:
| transport | result |
|---|---|
|
mcp__ccd_session_mgmt__send_message(the desktop/MCP path this thread is about) | delivered, 9 s ||
ListAgents+SendMessage(native peer messaging, previously absent on Windows) | delivered, 9 s, and the reply came back through it |ListAgentsdid not exist as a tool in my sessions before the change; it now lists peers. So native cross-session messaging is built and functional on native Windows — it was gated off, not missing. The desktop's older MCP path appears to have been failing because it was being caught by that same gate.---
How it was enabled — with the caveat stated up front
⚠️ This is disabled deliberately for Windows, and I do not know why. It may be untested there, or have known problems. I am reporting it as diagnostic evidence, not recommending it — anyone enabling it does so at their own risk, and the real fix is on Anthropic's side.
The engine's gate function resolves to
falseon Windows unless a flag overrides it. The override is a documented-in-binary env var, so this is not a patch or a bypass of authentication — no binary was modified, no credentials were touched:A full app restart is required, and this matters: the gate is evaluated when each session's CLI process starts, not when the app starts. This also explains @psangiovanni86-max's three-hour lag — sessions whose processes predated the swap kept working. In my case, sessions the app relaunched picked the flag up; one I opened afterwards, whose process survived the restart, did not — and that is precisely the session I used as the "recipient without the fix" above.
Note for MSIX/Store users specifically: we cannot downgrade (
[CCD-autoupdate] Disabled: MSIX install), so the 2.1.222 rollback several of you used is not reachable. For that population this was the only available path.---
Limits of this measurement, declared
2.1.229. @psangiovanni86-max's Squirrel install and @batesyx's environment show two different failure shapes; whether the sender-side gate explains both is untested.Two verification traps, both of which cost me hours
list_events/get_sessionread the app's store, not the transcript. They render the message and reportisRunning: truefor a message the engine never received. I confidently reported "the message reaches the recipient's transcript" three times based on this. It was false — measured by parsing the.jsonl, zero inbound records for the entire outage. This matches what @psangiovanni86-max and @nutballs already warned; I am repeating it because I made the mistake anyway.grepfor a marker string in the recipient's.jsonlproduces false positives — the sender's transcript also contains the message text. The valid criterion is atype:"user"record whose content contains the<cross-session-messageopening tag.Full reproduction on 1.30096.1 / engine 2.1.229, plus the thing that cost me a whole revert cycle: rolling back the APP does not roll back the ENGINE.
Windows 11 Pro 10.0.26200, Squirrel install (not MSIX). I took
1.30096.1deliberately to test it, reproduced the bug, and rolled back. Everything below is measured on this machine today.1. End-to-end reproduction in one log
Upgraded 09:48 local. Ten cross-session sends fired 09:50:09-09:50:44 (counted from
main.log,Sending message to session). Zero arrived — no recipient transcript gained atype:"user"record containing the<cross-session-messagetag. Then, ~1000 s later:Work it backwards: 1004 s before 10:07:27 is 09:50:43; 992 s before is 09:50:55. The send burst was 09:50:09-09:50:44. Those two sessions took a message from that burst, produced nothing, and were killed by the watchdog. Both were live sessions doing real work.
So this is not only "the message is lost" — it destroys the recipient.
One caution on reading these logs:
hadFirstResponse=falseon its own is not the signal. It appears in healthy cycles too — I have three in the same file. Onlyreason=no_responsepaired with the ~1000 s timeout discriminates.2. The gate is present in 2.1.229, and it short-circuits before the settings lookup
Earlier I offered a string test but could only infer the result. I now have both binaries locally, so here it is measured, with the positive control:
| grep |
2.1.221(accepting) |2.1.229||---|---|---|
|
cross-session-inbound| 0 | 10 ||
cross-session messaging disabled| 0 | 2 ||
cross-session(positive control) | 11 | 45 |And the gate itself, out of the shipped 2.1.229 binary:
This is why every
crossSessionInboundremedy in this thread fails on Windows. The kill switch returns beforeLUr(), so the setting is never read. Consistent with @WarmBed's analysis, and unchanged in 2.1.229.3. Rolling back the app does NOT roll back the engine
This is the part I most want people to see, because a failed rollback looks exactly like a successful one.
I reverted
app-1.30096.1->app-1.25927.0and relaunched. The app version read correctly. It was still broken. Process lineage shows why:The engine lives in
%APPDATA%\Claude\claude-code\<version>\, separately from the app, and the app appears to select the highest engine version present on disk.config.jsoncarries no engine pin (updaterLastSeenVersionis app-level only). So an app-only downgrade leaves the defective engine in place while reporting the version you wanted.The gate is in the engine. Verify the engine, not the app:
I suspect this explains at least some of the "I rolled back and it still fails" reports here. It is not enough for the old engine to exist — the new engine directory has to be gone or occupied, or it wins.
4. How to actually downgrade (verified, both halves)
Nobody has posted this, and it is not discoverable from the UI. Both artifacts are addressable by version.
Engine. Manifest and binary:
Channel pointers, as of now:
.../claude-code-releases/stablereturns 2.1.223,.../latestreturns 2.1.232.The manifest carries a per-platform SHA256, and I verified it describes exactly what ships:
Engines install to
%APPDATA%\Claude\claude-code\<version>\claude.exe.App (Squirrel only). The package URL pattern from
Squirrel-Update.log:Verified reachable for
1.25927.0(224.5 MB),1.28929.0, and1.30096.1. Old versions are not pruned.Two limits, stated rather than papered over. Each engine directory has a
.verifiedsidecar holding a hash that is not the SHA256 ofclaude.exe; I have not worked out what it covers, so a hand-placed binary may be re-verified or re-fetched. And MSIX/Store installs cannot downgrade the app at all ([CCD-autoupdate] Disabled: MSIX install), as @Bianco24siete noted — though the engine half above is independent of install type and may still help that population.5. The
stablechannel (2.1.223) does not contain the gate — measuredRather than tell people to hand-pin 2.1.221, I fetched
stableand tested it. Downloaded from the URL above, SHA256 verified against its own manifest before testing (a708ba81...fe722, 280,233,632 bytes, commit4535f697, built 2026-08-05):| grep |
2.1.221(known accepting) |2.1.223(stable) |2.1.229(broken) ||---|---|---|---|
|
cross-session-inbound| 0 | 0 | 10 ||
cross-session messaging disabled| 0 | 0 | 2 ||
cross-session— positive control | 11 | 11 | 45 ||
SendMessage— positive control | — | 52 | — |The gate strings are absent from 2.1.223. The same test separates the known-good build from the known-broken one, so it is discriminating rather than merely matching nothing.
Stated precisely: the absence of the gate code in 2.1.223 is measured. That 2.1.223 delivers is inferred — I did not run it, I rolled back to 2.1.221 which I had already measured end to end. But 2.1.223 predates 2.1.224, where the native cross-session path landed, and it is a supported channel pointer rather than a version someone has to know to ask for.
So the practical recommendation for anyone stuck on this is: pin the engine to
stable, not to a specific old build. That also survives the app-version churn described in section 3, because the engine is selected independently of the app.Current state here
Back on Desktop
1.25927.0/ engine2.1.221, engine process verified.2.1.229and1.30096.1are both blocked by occupying their paths with files, since deleting them alone just invites re-download.Independent confirmation of the engine/app split, plus one negative
CLAUDE_CODE_HARBOR_KITEresult on the Desktop MCP pathI agree with @wshallwshall that the running engine must be verified independently of the Desktop app version. My earlier tests already isolated the defect to the engine layer, but did not independently reproduce the specific app-rollback trap:
2.1.221accepted the Desktop-shaped<cross-session-message>and reached[API REQUEST] /v1/messages.2.1.227rejected the same frame before any API request withcross-session messaging disabled (kill switch)and produced zero stdout.So our earlier evidence independently established that the behavioral boundary is in the engine. What @wshallwshall adds is the important operational proof that rolling back the app alone may still launch a newer broken engine. We had not previously tested that exact old-app/new-engine combination.
I also tested the sender-side environment override reported above:
1.30096.1MSIX, official engine2.1.229.CLAUDE_CODE_HARBOR_KITE=1set in user~/.claude/settings.json.The flag was not simply ignored: new session registration files contained
messagingSocketPath, the correspondingcc-msg-*named pipes existed, andSendMessageappeared in the advertised tool set.However, one controlled
mcp__ccd_session_mgmt__send_messageprobe still did not deliver:Message sent to session ..., and Desktop loggedSending message to session ....type:"user"records with a<cross-session-messageopening tag.Therefore, on this machine the flag changed the exposed capability surface but was not sufficient to restore the Desktop MCP path in this probe. This is not a claim that the env var failed to load, and it does not disprove the successful report above. I have not yet independently tested a native
ListAgents+SendMessagesend, so that transport remains open.I also confirmed the current channel pointers directly:
stableis2.1.223, andlatestis2.1.232. I have not yet run end-to-end delivery on either, so I am not treating either version as fixed until the recipient transcript, first response, result, and round trip all pass.Confirmed data point (Windows): the
CLAUDE_CODE_HARBOR_KITE=1flag DID restore the Desktop MCPsend_messagepath on our machine — transcript-verified.Environment: Windows 11 Pro 10.0.26200, Desktop
1.30096.5, bundled engine2.1.229(the kill-switch gate strings are still present in the binary here — so this is a workaround, not a fixed engine).We were fully dark for ~3 working days: every cross-session
send_messageto an idle session was refused, the recipient hung withhadFirstResponse=false, and the app watchdog killed it at ~980s.Following @Bianco24siete's finding, we set in user
~/.claude/settings.json:and did a full restart (a machine reboot in our case).
Result — messaging restored, verified at the transcript level (not the app's "message sent" popup, which we'd already learned lies):
.jsonlas a propertype:"user"<cross-session-message …>record — delivered.hadFirstResponse=true, no ~980s hang — processed.This is an independent positive on the Desktop MCP
send_messagepath specifically — @Bianco24siete reported it working; @WarmBed reported the flag loading (sockets/cc-msg-*pipes/SendMessagetool all appeared) but the MCP path still not delivering in one probe. On our machine it delivered on that path. So the flag's effect on the legacy Desktop path appears machine/timing-dependent, but it can and did fully restore it here.Huge thanks to @Bianco24siete for isolating the flag and the sender-side framing — that's what got us unblocked after three lost days — and to @WarmBed, @wshallwshall, @DEzioA and @Heeyoung-Ahn for the binary-level forensics and the transcript-verification discipline.
Ask to Anthropic: this thread still has no maintainer response, while the regression has caused multi-day outages for anyone relying on cross-session coordination on Windows. Two requests:
ccd_session_mgmtinjection isn't caught by the native-feature Windows kill switch — or ship a supported, documented setting that works on Windows. TheCLAUDE_CODE_HARBOR_KITEroute enables a feature that is deliberately disabled on Windows "at our own risk," which shouldn't be the only way to restore basic messaging.send_messagereport delivery/refusal truthfully — the false "sent" confirmation (while the recipient never received anything) is what made this so costly to diagnose.Negative datapoint on the exact same app+engine as the restoration report above — Desktop 1.30096.5 / bundled engine 2.1.229, STOCK config (no flag), Windows Server: message rendered in the recipient's pane but never reached its transcript
Environment
%APPDATA%\Claude\claude-code\2.1.229\claude.exe; 2.1.227 staged beside it) — the identical app+engine pair as @007ArunSharma's flag-restored positive.CLAUDE_CODE_HARBOR_KITEand nocrossSessionInboundkey; engine not pinned or swapped. (A standalone CLI 2.1.220 on PATH is a separate install, not what Desktop runs.)What happened (2026-08-15, one controlled probe as part of a planned channel re-test)
mcp__ccd_session_mgmt__send_messagewith a ~430-character plain-text message; the call returned success..jsonl, per the discipline established upthread): NOT delivered. The recipient's last transcript record is timestamped ~30 seconds BEFORE the send — zerotype:"user"<cross-session-message>records, zero queue-operations, nothing at all after the send.%APPDATA%\Claude\logs\main.logexcerpts and session IDs if useful.Net: with app, engine, and (per @DEzioA) the gate strings all identical to the restoration report, stock config still wedges here — the flag (or something machine-dependent it interacts with) is doing the restoring, not the build. OS is the other delta: Windows Server 2022 vs Windows 11 Pro. I'll try the
CLAUDE_CODE_HARBOR_KITEroute on this machine next and report the result either way, since its effect looks machine-dependent (one negative probe from @WarmBed, two transcript-verified positives).+1 to both asks: a supported fix for the Windows path, and truthful delivery/refusal reporting from
send_message— the false "sent" (and now, a false pane render on the recipient) is what makes this expensive to diagnose.Follow-up datapoint (promised in my 2026-08-15 comment): CLAUDE_CODE_HARBOR_KITE=1 restores cross-session send_message on the same machine that reproduced the wedge flag-off.
Environment: Windows Server 2022 Datacenter (10.0.20348), Claude Desktop 1.30096.5, bundled engine 2.1.229. Same two-pane Desktop setup (two sessions, same window, same cwd) as my earlier report.
Flag-off (earlier the same day): first delivered handshake (~430 chars) auto-started the recipient's turn, then the recipient wedged — 10m+ zero output, spinner active — matching this issue's profile. Session kept un-archived as evidence.
Flag-on: added "env": { "CLAUDE_CODE_HARBOR_KITE": "1" } to ~/.claude/settings.json, rebooted the VM, re-ran the same graduated protocol. Result: 4/4 legs delivered and verified against the recipients' transcript .jsonl files (not the pane render, which produced a false positive in my flag-off test):
Notes: delivery to a busy session queues until that session goes idle (expected behavior as far as I can tell, but it's the exact spot where a wedge turns into silent message loss, so worth covering in any fix's tests). The wrapper's XML-entity escaping of < > & decodes cleanly.
So on this machine the kill-switch bypass fully restores the path — consistent with the two positive reports up-thread, and it suggests the gated code path itself works on Server 2022 and the kill-switch is the only thing standing between Windows users and a working feature. Happy to run further diagnostics on either configuration; I've kept the flag-off wedged session's transcript.
Mixed result after today's Desktop update — active-session delivery now clean, but idle-session wake-up still hangs (recovery-via-new-input also failed this time)
Environment: Windows 11 Pro 10.0.26200, Desktop app updated 2026-08-16 (Owner-confirmed update, exact new version not independently verified from this session), bundled engine directory still shows
2.1.229. Two coordinator-managed CCD sessions in the same workspace, tested via themcp__ccd_session_mgmt__send_message/list_events/get_sessiontools (not the pane render — we verify against the target session's own transcript, since a prior incident here taught us render ≠ delivery).Test 1 — target session busy (in-flight turn) at send time
send_messagereturned: "Message queued ... will be processed after the in-flight turn finishes if that session stays healthy."list_events) immediately.Test 2 — target session idle at send time
send_messagereturned an unconditional success (no queuing language).get_sessionshowedisRunningflipfalse → trueright after send (so the session did register the wake trigger).isRunningstayingtruethe whole time. Matches this issue's original profile (spinner active / zero output).isRunningstilltrue,lastActivityAtfrozen at the first message's timestamp).Net: the fix appears to help the "recipient already mid-turn" path, but the "recipient idle, needs to be woken from a cold state" path — which is the more common real-world case for cross-session handoffs — still reproduces the hang, and our previously-working recovery trick no longer reliably clears it. Happy to leave the hung session alive if a maintainer wants any additional diagnostics pulled from it before we archive/restart it.
The Windows gate is implemented differently in
stablethan inlatest, and theCLAUDE_CODE_HARBOR_KITEworkaround in this thread only works on one of them.Windows 11 Pro 10.0.26200, Squirrel (non-MSIX) install. Everything below is measured on this machine today unless marked inferred.
1. The two gate functions, verbatim
All three binaries SHA256-verified against their own manifests before reading —
2.1.224=879f0d7e7eee…,2.1.229=5736c66be98a…,2.1.233=8ae35d4125….On
stable, the platform check returns first and no environment variable can reach past it. Onlatest, the env check runs first and short-circuits totrue.So
latestis more permissive on Windows thanstable— it added a server-side flag (tengu_harbor_kite_win, defaultfalse) and moved the override ahead of the platform check. TheCLAUDE_CODE_HARBOR_KITE=1workaround reported by @007ArunSharma and @DonPuls works on 2.1.229/2.1.233 and silently does nothing on 2.1.224.A warning on method, because it nearly caught me: a string-count pass on 2.1.224 returns
cross-session messaging disabled= 0 andtengu_harbor_kite_win= 0, which reads as "stable is clean." That is wrong. 2.1.224 gates Windows with a bare early return carrying no flag and no message string, so the discriminating strings don't exist there. For a gate question, extract the function; adjacent string counts fail exactly when the implementation changes shape.2. Delivery, measured against recipient transcripts
| engine | probes | delivered |
|---|---|---|
| 2.1.229 (Desktop 1.30096.5) | 8 | 0 |
| 2.1.221 (Desktop 1.25927.0) | 1 | 1, round trip in 2.1 s |
The 8 covered busy and idle recipients across four repositories. The 1 used the same sender and the same recipient as one of the failures, so only the engine differed.
Successful delivery, from the recipient's own
.jsonl:On all 8 failures there was no
queue-operationrecord and nouserrecord — nothing at all in the recipient's transcript. The drop precedes the queue.Stated honestly: that A/B moved two variables (app and engine), so by itself it does not localise the defect. What localises it to the engine is the gate functions above plus @WarmBed's standalone isolation of 2.1.221 vs 2.1.227.
3. A cheap test anyone can run
Send one tokened probe, then search all transcripts for that token. If it appears only in the sender's transcript, it was not delivered. No census, no rate, no waiting — and it is immune to the contamination that ruins substring counts once sessions start discussing the outage.
Do not trust the sender-side "Message sent to session X" popup. It reports success on every one of the 8 failures.
4. The framing, which I think is the actual bug
The documentation states: available on macOS and Linux, including Linux inside WSL 2. Claude Code doesn't offer cross-session messaging on native Windows.
If that is intended, then this issue is not a regression — and the defect is that the feature is advertised as working rather than being cleanly absent. On native Windows
SendMessageis exposed, the sender is told the message was sent, the recipient wakes and starts a turn, and nothing is ever enqueued. The docs say a session without the feature should not even recognise/list-agents.A documented-unavailable feature that reports success is worse than one that errors, and it is a smaller fix than making messaging work.
Related: #24798 was closed on 2026-08-17 with "as of v2.1.224 your Claude Code sessions can message each other", with no platform qualification.
5. The wedge is not limited to peer messages
Recipients that never receive a message still wedge, and so do sessions receiving ordinary typed input:
That is @arthurmoraesfernandes-afk's
isRunning held by unechoed inputsignature. Inferred, not instrumented: the triggeringLocalSessions.sendMessagecarriedmessageLength=9, imageCount=2, which exactly fingerprints a 9-character prompt plus two screenshots I typed myself — not a peer message. If that reading is right, the wedge is broader than this issue's title suggests.A resend with
priority=nextreclaims the session;priority=(default)does not.Data point from a Windows Desktop setup: the 2.1.234 fix cannot reach Desktop users yet.
versionfield, including sessions created fresh after a full app restart.hadFirstResponse=false, reason=no_responsein main.log), no error on either side. Reproduced 6+ times (Aug 16-18), on brand-new sessions, outside any platform incident."crossSessionInbound": "accept"in~/.claude/settings.json(supported since v2.1.224 per the docs) + full app restart → no effect on 2.1.229.Could a Desktop build embedding engine >= 2.1.234 be shipped? Until then, Desktop users cannot benefit from the upstream fix.
Update, and likely resolution for our variant: today's Store build 1.32885.1 ships engine 2.1.234 (verified in the transcript
versionfield). Cross-session messaging works again on our machine — full round-trip verified: send triggers the recipient's turn, and the reply triggers a turn on the sender's side. Both directions, first try, after 7 days of silent drops on 2.1.229.For anyone stuck: check
Get-AppxPackage Claudefor 1.32885.1+, and confirm the engine version in any fresh transcript. Note that sessions still holding an undelivered "ghost" message from the broken engine keep showing the stale card — those messages are gone, but the sessions themselves work fine.Adding a quantified before/after for the regression, confirmation of the fix, and a testing trap that produced a false negative for me.
1. Quantified regression — 82% → ~0% on the affected builds
I paired every
Resuming session <id>in the desktop logs with the next event for that id — eitherMapping internal session <id>(a CLI process attached) or ahadFirstResponse=falsetimeout (it never did):| Log | Desktop version | Result |
|---|---|---|
|
main1.log(09–16 Aug) | 1.26832.0.0 / 1.28929.0.0 | 121 attached / 26 failed = 82% ||
main.log(from 16 Aug 12:49) | 1.30096.5.0 | 11 attached / 36 failed = 23% || — rate-limit-blocked sessions only | 1.30096.5.0 | 0 of 12 |
The 82% comes from rotated logs, so it's a real before-baseline on the same machine and the same sessions. On 11 Aug at 01:32, three sessions woken by
send_messageattached within 1–2 seconds each.Practical impact of the broken window: two sessions stranded by a 5-hour rate limit stayed dead for ~11 hours overnight.
Mapping internal session <id>is the reliable indicator of an attached process — noteisRunningreports true throughout the failed state, so the UI shows dead sessions as live. That cost me several hours of misdiagnosis.2. ✅ Confirmed fixed in Desktop 1.32885.1.0 / engine 2.1.234
Corroborating @Aturion31. Tested against a session that had been paused ~10 hours, with the app up ~50 minutes:
Attached within seconds. Same machine, same session type that went 0-for-12 the night before.
3. ⚠️ Testing trap: don't test in the first minutes after the update relaunch
My first test on this build reported it as still broken, and that was wrong. It ran 70 seconds after the app relaunched for the update — the app logged 43 startup lines in the surrounding two-minute window. The message was swallowed and the session died at 999s with
hadFirstResponse=false, which looks identical to the regression.That is almost certainly #86326 (a message delivered to a session that is still starting up is silently lost), not this issue. Anyone verifying the fix should let the app settle for a few minutes and prefer a long-idle target, or they'll reproduce the startup bug and conclude the regression is still live.
4. Diagnostics that were useful, for the archive
Mapping internal session <id>— the only reliable "a CLI process really attached" signal.isRunningis not.LocalSessions.setFocusedSession— tells you a human opened the session, vs a bareResuming sessionmeaning a message triggered it. Essential for telling a real wake from an accidental click during testing.Message sentvsMessage queuedfromsend_message— "queued" means the target was mid-turn, so that send tests nothing.5. One caution for anyone who built retry tooling during the outage
On the broken build I ran a recovery loop that fired 19 retries at two rate-limited sessions over ~6 hours on a ~16-minute cadence. Since each undelivered message occupied the session for ~1000s, that plausibly kept both unrecoverable for the whole period — the retries were likely doing harm rather than helping. I have not re-verified this on 2.1.234, so treat it as a property of the broken builds only; but if you wrote a retry loop while this was broken, it's worth revisiting rather than leaving armed.
Environment: Windows 11 (26100), 15.7 GB RAM. Broken on Desktop 1.30096.5.0; working on 1.32885.1.0 / engine 2.1.234.
Another negative datapoint on Desktop 1.32885.1 / engine 2.1.234 (Windows 11, MSIX Store build) — single-shot protocol, transcript + log verified.
Environment: Windows 11 Home 10.0.26200, Desktop 1.32885.1 via MSIX auto-update landing 2026-08-19 11:51 local, engine dir
%APPDATA%\Claude\claude-code\2.1.234(fresh install by the update, Authenticode Valid). Recipient session idle at send time.Following @CptCabbageee's warning that a failed delivery jams the recipient (~1000s) and timer-based retries hold it jammed, this was one send, zero retries:
13:14local — Desktopmain.loglogsSending message to session <id>.queue-operation/enqueue, no user turn, nothing.Mapping internal session <id>line ever follows the send — while in the same minutes, other (human-focused) sessions'Mapping internal sessionlines fire normally, matching thesetFocusedSessiondiscriminator described above.isRunningreportedtruefor the recipient throughout, consistent with every prior report that the session store lies while the CLI never attached.So on this machine 1.32885.1 / 2.1.234 reproduces the original signature exactly — corroborating @CptCabbageee's 2.1.234 result and contradicting none of the mechanics; combined with @Aturion31's working 2.1.234 round-trip, the same-version/different-outcome split is still unexplained. Happy to run a specific diagnostic if someone from the team wants data from a failing 2.1.234 machine.
Authored by AI (Claude - Opus 5 w. ultracode); approved by me - a human :)
---
Negative datapoint on Desktop 1.32885.1 / engine 2.1.234, run specifically to avoid the false-negative traps @CptCabbageee documented, since the fix status is currently split.
I also have to withdraw an earlier negative of my own from tonight — it was confounded. Details in §3, flagged so it isn't counted as a second failure.
Environment: Windows 11 Pro 26200 · Desktop 1.32885.1 (MSIX, auto-update landed 21:23:47 local) · engine 2.1.234 (
Using Claude Code binary at: …\claude-code\2.1.234\claude.exe) · Max subscription.1. Protocol — controlled against the known traps
Mapping internal session, 0 timeouts — i.e. every earlier delivery to it attached cleanly, so it carried no undelivered message from the broken engine.Message sent, notMessage queued— the target was not mid-turn, so the send actually tests something.setFocusedSessionpreceded the send.Mapping internal session, notisRunning.2. Result: message lost
Complete log record for the target, start to finish:
Counts for the whole evening, scoped to that session id:
| signal | count |
|---|---|
|
Mapping internal session <T>| 0 ||
Sending message to session <T>| 0 ||
[CCD start-timing] <T>| 0 ||
drained N deferred send(s) for <T>| 0 |The wake fires and nothing attaches. A unique token in the message body appears only in the sender's transcript, never in the recipient's.
One refinement to the "transcript mtime frozen" description in #87615: the recipient's
.jsonlmtime did move, to 22:16:08 — exactly the teardown second — while the file gained no message record (token count 0, still 96 records). So mtime is not a reliable "did it arrive" probe on its own; the file is touched at cycle teardown regardless. Byte-size/record-count or a token grep is the safer check.3. ⚠️ Withdrawing my own earlier negative from tonight
I ran an earlier probe on this same build at 21:32 which also failed, and it is not sound evidence. Three defects, all of which @CptCabbageee's notes predict:
setFocusedSessionfired 4 s before the send, so the target was not in a clean idle state.Only the §2 run should be counted.
4. Two diagnostics that may explain why reports disagree
The log signature depends on the target's state, not only on the build. Across four builds I get three distinct signatures, and two probes on this build four minutes apart produced two of them:
| target state |
Sending message|Resuming/Loaded N| outcome ||---|---|---|---|
| mid-turn | ✅ logged | — |
drained 1 deferred send(s), lost || warm / recently bound | ✅ logged | ❌ | no
Mapping, lost || cold / unbound | ❌ never | ✅ | no turn, watchdog, lost |
This is independently visible in @JLWsoftware's data on #86498 (busy recipient → bare
Sending message; paused recipient → resume with no send line). It means "noSending messageline" and "a bareSending messageline" are the same defect seen against different target states — worth controlling for before attributing a signature change to a version.seconds_since_stderr=neverdoes not mean the CLI process never started. On an earlier build I have a cold-target case where the binary is logged andLoaded 827 transcript messagessucceeds, and the field still readsnever997 s later. It only means the CLI emitted no stderr — true both when it never starts and when it starts with nothing to run.Minor caution for #87615's echo-reaping chain:
isRunning held by unechoed input at resultfires here on days with zero cross-session sends (2 of 5 occurrences), andreaped … stale pendingEchoUuidsnever appears in my logs at all. It looks like general echo tracking rather than a cross-session-specific symptom.5. Summary
On this machine, with the traps controlled for, 1.32885.1 / 2.1.234 does not deliver cross-session messages to a cold target. That is one clean negative, not a claim that the fix is universally absent — @Aturion31 and @CptCabbageee's positives are on the same versions, so something environmental or path-dependent still separates the two outcomes. The target-state table in §4 is my best guess at where to look.
Unfortunately, I think the title of this bug does not indicate its severity. This is a serious regression.
Following up on the
CLAUDE_CODE_HARBOR_KITEfindings above (thanks @nutballs, @WarmBed, @Bianco24siete, @DEzioA) — rather than just asking for the Windows gate to be lifted, it might be worth asking a more specific question: is the underlying pipe already access-controlled, or is the gate standing in for that missing check?Concretely, for
cc-msg-*/messagingSocketPathto be safe to enable by default, I'd expect two things:If both of those are already implemented under the hood, that would go a long way to explaining why it's safe to flip this on, and might be worth documenting so people aren't just discovering it via binary string analysis. If they're not both in place yet, that seems like the actual blocker worth fixing, rather than the flag being toggled on as-is — since as WarmBed's test showed,
HARBOR_KITE=1doesn't even reliably fix delivery yet, so there's not an urgent reason to trade an unverified security boundary for an unconfirmed fix.Would a maintainer be able to confirm which of the two (if either) is currently in place? Happy to help test once there's something concrete to verify.
Authored by AI (Claude - Opus 5 w. ultracode); approved by me - a human :)
---
✅ Fixed here on Desktop 1.34493.1 / engine 2.1.237 — all three delivery paths, 3 of 3. This supersedes my negative datapoint above, which was on 1.32885.1 / 2.1.234.
Environment: Windows 11 Pro 26200 · Desktop 1.34493.1 (MSIX, auto-update landed 18:40 local) · engine 2.1.237 · Max subscription. Same machine, same protocol, same target class as the failing test I posted two days ago.
1. Results — cold, warm and queued all deliver and run a turn
One target (idle 5 days, ghost-free: 7 prior sends / 7 attaches / 0 timeouts across both log roots), probed in three states back to back. Each success left the session in the state needed for the next test.
| path | tool returned | attach | transcript | turn | verdict |
|---|---|---|---|---|---|
| cold (idle 5 d) |
Message sent|Mapping+2 s |queue-operation enqueue+userrecord |start-timing, healthy 178 s | ✅ || queued (target mid-turn) |
Message queued|drained 1 deferred send(s)→Mappingsame second | enqueue +user| healthy 59 s | ✅ || warm (bound, idle between turns) |
Message sent|Mappingsame second | enqueue +user| healthy 46 s | ✅ |Cold path, in full:
The warm probe's transcript records include assistant
thinking,textandtool_use— the recipient read the message and acted on it, not merely received it.2. The queued path is the one worth highlighting
On 1.32352.1 I logged the deferred-send variant failing silently: the drain fired and the payload simply evaporated, with the target left perfectly healthy —
— no spinner, no watchdog, no symptom anywhere, sender told "queued". That was the nastiest variant precisely because nothing surfaced it.
On 2.1.237 the same
drained 1 deferred send(s)line is now followed by aMappingand a real responding turn. Anyone verifying this fix should test that path explicitly: a build could attach cold targets correctly and still lose everything sent to a busy session, and the failure would be invisible.3. Clean before/after on one machine
| date | build | engine | target | result |
|---|---|---|---|---|
| 2026-08-19 22:00 | 1.32885.1 | 2.1.234 | cold, ghost-free, idle 4 d | ❌ no attach, 960 s watchdog, payload lost |
| 2026-08-21 19:05 | 1.34493.1 | 2.1.237 | cold, ghost-free, idle 5 d | ✅ attach +2 s, turn ran |
Both runs used the same controls: settled well past the last app restart, long-idle ghost-free target,
Message sent(notqueued), single send, no retries, no focus click, verdict taken fromMapping internal sessionrather thanisRunning.⚠️ This does not retroactively validate the 2.1.234 fix reports. @mouarg, @Aturion31 and @CptCabbageee reported success on 2.1.234, and 2.1.234 failed here under exactly the protocol above. Either the fix was partial or environment-dependent on 2.1.234 and complete in 2.1.237, or those machines differed in some way that mattered. I cannot distinguish those from here, so I am not asserting either.
⚠️ App and engine moved together (1.32885.1 → 1.34493.1, 2.1.234 → 2.1.237), so I cannot attribute the fix to one or the other.
4. ⚠️ 1.34493.1 MOVED THE LOG DIRECTORY — this will break your tooling
Anyone still grepping the old path will read a dead file and may conclude nothing is happening.
%LOCALAPPDATA%\Packages\Claude_<pkg>\LocalCache\Roaming\Claude\logs\— its last line isbeforeQuitForUpdate handler fired, going down for update.%LOCALAPPDATA%\Claude\Logs\main.logThe app is still MSIX (
windowsStore=true), so this is not a packaging change — just a relocation. History is now split across two roots: any before/after census or "has this session ever failed a delivery" check must enumerate both, or it silently undercounts.5. Minor correction for the echo-reaping chain in #87615
That report presents
isRunning held by unechoed input at result→reaped N stale pendingEchoUuidsas part of the loss chain. On this machine that line fires:reaped … stale pendingEchoUuidsnever appears here at all. It looks like general echo tracking rather than a cross-session loss signal, so it is probably not the thread to pull.6. Scope
One machine, one target, one probe per path, on the cold/warm/queued states as defined above. Not a claim that every environment is fixed — the unreconciled 2.1.234 split in §3 is reason enough for others to re-verify rather than take this as universal.
@WarmBed ... I second the above suggestions... It would be helpful if you retitle this issue to convey the urgency.
@wshallwshall Done — retitled to convey the severity and current split state.
Status on my machine, for the record: Desktop 1.34493.1 (MSIX Store channel) / engine 2.1.237 — cross-session messaging verified working stock (no flag, no workaround): single-shot receipt test, message reached the idle recipient's transcript, session woke, replied, and the ack round-tripped back. This matches @InfiniteBSOD's 3-of-3 report on the same build.
So the picture as I read the thread today:
CLAUDE_CODE_HARBOR_KITE=1works there (env check precedes the platform gate, per your decompile).I'll keep the issue open until the stable channel ships a fixed engine and the thread goes quiet on new reproductions. If anyone still hits this on 2.1.237+, please post the build/channel — that would be important evidence of a staged rollout rather than a version fix.
Ran a structured attempt to reproduce this. It did not reproduce here — but the scope is narrow enough that I'd rather lead with two methodological findings than with the negative result, because I think both affect how everyone else in this thread is measuring.
Scope, up front: Claude Code engine
2.1.237, Claude Desktop1.34493.1(MSIX/Store), Windows Server 2025 (10.0.26100). That build is neitherstable(2.1.231) norlatest(2.1.241), so nothing below speaks to "stable channel still affected."---
1. The transcript oracle is blind during deferred holds
This is the finding I'd most like other people to check, because it may mean some reproductions have been filed as the wrong bug.
When a message is delivered to a session that is mid-turn, Desktop holds it and the recipient's transcript JSONL gets no record at all until the in-flight turn ends and the send drains. Two measurements:
| Delivery | Sent | Record written | Blind window |
|---|---|---|---|
| into a mid-turn session | 13:08:15.384Z | 13:08:38.014Z | 22.6 s |
| into a mid-turn session | 13:10:14Z | 13:12:33.9Z | 139 s |
In both cases the drain is marked by
[LocalSessionManager] drained 1 deferred send(s), and the peer record's timestamp lands within ~100 ms of that drain — not of receipt. For contrast, a send to an idle session wrote its record within ~50 ms of receipt, so the oracle is sound there.The window scales with the recipient's turn length.
Why this matters: if a session wedges on the deferred path, the transcript contains no peer record whatsoever. Anyone whose detection logic is "did a cross-session envelope appear, and was it answered?" will score that as message never arrived — i.e. a silent-drop bug — rather than as this one. The issue's own signature (
last_message_type=user,hadFirstResponse=false) is Desktop-side in-memory buffer state, which is exactly the state that has no transcript counterpart during the hold.Also worth noting: the MCP return string differs by path, which is a cheap way to tell which branch you're on.
Message sent to session ...Message queued for session ...; it will be processed after the in-flight turn finishes if that session stays healthy.2. On Windows, the log path in this issue is dead
Desktop
1.34493.1relocated the log directory. Anyone grepping the path named in the issue body is reading a file frozen at their last major version bump, and will conclude "no log data" with full confidence.Mine was stale by ~7 weeks and I nearly reported "the confirmation path is unavailable" on that basis. The live file is also truncated at every app launch (line 1 is
Starting app), so capture it withtail -Frather than reading it after the fact.Two corrections to the signature itself, from the live log:
hadFirstResponse=falseis not diagnostic on its own. My log contains[CCD CycleHealth] healthy cycle for local_… (5s, hadFirstResponse=false)— a perfectly healthy 5-second cycle. The discriminator isunhealthyplusreason=no_responseplus a long duration.timed out after, without the tag or the extra fields. The emitted line does not carry the full field set quoted in the issue body, so grepping the quoted line matches nothing even when the event fires.---
3. The negative result, with honest scoping
30 deliveries, 0 wedges. Per-cell rather than pooled, because the hazard in this thread is clearly state-conditional:
| Cell | Trials | Wedges |
|---|---|---|
| warm / idle (~2 min old) | 20 | 0 |
| deferred / mid-turn | 2 | 0 |
| unbound / re-attach (~6 770 s since last Stop hook) | 5 | 0 |
| cold (no live CLI process) | 0 | untestable — see below |
Watchdog across the full capture: 65
healthy cycle, 0timed out after, 0unhealthy, 0reason=no_response. The warn/error channel was demonstrably alive in the same window, so those zeros are real zeros rather than a silent logger.What I think this does and doesn't support. The ~35% per-dispatch rate reported for 2.1.227 is strongly excluded on this build in the cells I could reach. I don't think it supports a tight bound: my trials cluster into ~12 distinct session-states inside one Desktop process lifetime, so the defensible upper bound on a residual rate is roughly 20%, not "<5%".
The cold cell — the strongest documented trigger in this thread — I could not sample at all. On this build there's no way to end a session's CLI process while keeping the session listable: the session context menu offers only Pin / Mark as unread / Rename / Fork / Move to group / Archive / Delete. Archiving removes it from
list_sessions, so a "no delivery" reading from an archived target would be fabricated rather than measured. Note also thatIdle timeout reached, disconnectingdoes not produce a cold session — all 10 CLI processes stayed alive across repeated idle disconnects, so "unbound" and "cold" are genuinely different states and only the former is reachable here.If someone on
stablecan run the deferred-path check in §1, that seems higher value than more warm-path trials.Not a reproduction — a problem with how we've all been measuring this
Testing on 2.1.237 I did not reproduce the wedge. But before that result gets counted as another "fixed for me", I think the method most of us are using has a hole in it, and it points the wrong way.
Environment: Windows 11 Pro 26200 (VMware VM) · Claude Desktop 1.34493.1 (MSIX/Store) · engine 2.1.237 · stock install (
settings.jsonis{}).The transcript record is written at drain, not at receipt
Several reports here (mine included, initially) use the recipient's
.jsonltranscript as the oracle: if a peer record appears and no answer follows, call it a wedge; if no peer record appears at all, call it a silent drop and file it elsewhere.That second branch is the problem. I sent a probe into a provably in-flight turn — the tool returns a distinct string in that case:
Then sampled the recipient's transcript once per second:
Correlating both clocks (log is local, transcript is UTC):
| clock | event |
|---|---|
| 08:28:23 | Desktop accepts, returns "queued" |
| 08:28:25 |
drained 1 deferred send(s)|| 13:28:25.042Z |
queue-operation/enqueuerecord written || 13:28:25.069Z |
userrecord written || 13:28:27.272Z | assistant answer |
While a message sits deferred, the recipient's transcript contains no record of it at all. The records appear at drain.
Why that matters for this issue's denominator
If a session wedges while a message is deferred, the transcript has nothing in it — no enqueue, no peer record. Under the usual rule that reads as "silent drop", which gets filed as #86237 / #86298 and excluded from this issue. So the transcript oracle is blind to precisely the deferred-path wedge, and some true positives here may already be sitting in those other threads.
I'd suggest anyone reporting either way says which oracle they used. The Desktop watchdog reads its own in-memory buffer, not the transcript (
r.messageBuffer.some(e => e.type === "assistant")) — which is also why the reported signature islast_message_type=user: a message Desktop holds that the CLI never processed. That buffer, not the JSONL, is where this is visible.The log path moved, which breaks the confirmation path in this issue
On Desktop 1.34493.1 the log directory relocated on Windows:
Reading the old path yields a confident, wrong "no log data". The live file is also truncated at every app launch, so capture it with
tail -Fbefore running anything.Corrected grep strings
timed out after— no[CCD]tag, no id, no field assumptions. The emitted line does not carry the extra fields quoted upthread.hadFirstResponseandreasoncome from the[CCD CycleHealth]line. Valid reasons:permission_stall|incomplete_response|no_response.hadFirstResponse=falseis not diagnostic on its own — this machine logshealthy cycle … (5s, hadFirstResponse=false). The discriminator is unhealthy +reason=no_response+ a long duration.no sign of the turn a re-adopted process. Corroboration sets that only grep warn will miss it.Two smaller corrections
operation:"remove"is not a drop signal. It is the normal path for a delivered mid-turn message — my own transcript shows a message that was delivered and acted on goingenqueue→remove.Scope of my clean result — please don't over-read it
15 sends, 8 sessions, 1 Desktop instance, 1 machine, ~40 minutes, all answered, no wedge candidate. Three took the deferred branch.
I am explicitly not offering that as evidence the bug is fixed:
Thanks @WarmBed for renaming the issue. I've been burned before hoping this was fixed, but I've now tested it in three environments. Claude wrote the two summaries above with deep testing inside two VMs, one Windows Server 2025 and the other Windows 11.
I've also just upgraded my developer PC and it appears to be working well. If this holds, I'd recommend closing this issue.
Closing per @wshallwshall's recommendation above — three independent environments (two VMs + his own dev machine) now confirm the fix, matching my own verified round-trip on 1.34493.1 / engine 2.1.237 and @InfiniteBSOD's 3-of-3 report on the same build.
Summary for anyone finding this later:
CLAUDE_CODE_HARBOR_KITE=1) varied across engine builds — see the decompiled gate functions and version timeline upthread.versionfield), and channel (stable/latest/MSIX/Squirrel) — per @wshallwshall's methodology notes above, also rule out the "transcript blind window during a mid-turn hold" measurement trap before calling it a silent drop.Thanks to everyone who dug into this with real evidence over the past two weeks — @wshallwshall, @CptCabbageee, @InfiniteBSOD, @Aturion31, and others. Reopening if new reproductions surface.
Still reproduces tonight (2026-08-25) on Desktop app 1.34493.1.0, CCD engine 2.1.237, Windows 11 Home 26200 — the exact version this issue's title says is fixed for some installs.
One mcp__ccd_session_mgmt__send_message call to a peer local session returned success (
{"success":true,"message":"... -> <target>","msg_id":"..."}), and the target's transcript viewer showed the message. But grepping main.log for that specific send (matched by exact message length, 270 chars, in the surrounding minute) turns up nothing: noLocalSessions.sendMessageline, noSending message to session, noMapping internal session— while the same target session logged clean sendMessage -> Sending message -> Mapping triples for dozens of other sends, from other fleet sessions, both immediately before and after this one that same evening.That's the "no log line at all" variant flagged upthread on #86498 (Dimaxia, 2026-08-14 — "the fourth case: no log line at all — the tool still returned success"), so it's still live on 2.1.237, not just on the older builds. Happy to pull the exact log window if useful.
Negative datapoint — engine
2.1.246/ Desktop1.37937.1(MSIX): no reproduction in 4.81 days across 153 sends.Posting because @CptCabbageee reports this still live on
2.1.237two days after the close, so a clean negative on a later MSIX build is evidence on whether that split is a version boundary or a staged rollout. There is also a measurement trap below that will otherwise manufacture false "fixed for me" reports.Same machine as #86088 (closed as a duplicate of this issue yesterday), where I previously measured 14
reason=no_responsein 4.47 days (≈3.1/day) on engine2.1.229.Environment: Windows 11 Pro for Workstations 10.0.26100 · Desktop
1.34493.1→1.37937.0→1.37937.1across the window · engine2.1.237→2.1.241→2.1.246· MSIX/Store · stock,CLAUDE_CODE_HARBOR_KITEunset.Result
Window 2026-08-21 18:54:10 → 2026-08-26 14:26:28 (4.81 d), counted in
%LOCALAPPDATA%\Claude\Logs\main.log:| signal | count |
|---|---|
|
Sending message to session| 153 ||
Mapping internal session … to CLI session| 204 ||
reason=no_response| 0 ||
isRunning held by unechoed input at result| 0 ||
timed out after Ns of inactivity| 0 |Ruling out the near misses, since "0" is only worth as much as what it excludes:
unhealthy cycleverdicts in the window are bothreason=api_error, hadFirstResponse=true— the pre-existing category, not this bug.hadFirstResponse=falsedoes appear 6 times, but every one of them readshealthy cycleat 1–21 s (short cycles that ended before a first response). None carriesreason=no_response.Scope limit — this does not cover the deferred path
drained N deferred send(s)appears 0 times in the entire window. Either no send ever landed on a mid-turn target here, or that line no longer appears in this build — I can't separate those two from the log alone. So this negative covers cold and warm delivery only and says nothing about the queued/mid-turn path, which is exactly where @wshallwshall's transcript blind-window applies.⚠️ The log file moved on 2026-08-21, and the old one is still sitting there
This is the part I'd most like others to check, because it silently narrows your measurement window to zero while still returning a plausible-looking result.
%APPDATA%\Claude\logs\main.log— frozen at 2026-08-21 18:45:36, last linebeforeQuitForUpdate handler fired, going down for update%LOCALAPPDATA%\Claude\Logs\main.log— liveGrepping the old path for
reason=no_responsereturns 0. Not because the bug is fixed, but because nothing has been written to that file in five days. Both of my earlier comments on #86088 cite the old path, as does the Root cause section of this issue. Anyone re-checking against the path used earlier in this thread will read a dead file and report a false negative. Worth a line in the repro instructions.On the surviving variant
None of this speaks to @CptCabbageee's "no log line at all" case. That is the second failure point I tried to separate out in #86088 —
sendMessage → Sending message → (no Mapping), i.e. the child is never wired up, as distinct from the child spawning and then wedging against the API (Resuming → Loaded N transcript messages → no start-timing). A fix to the platform gate would not necessarily touch it, so I'd read "fixed in 2.1.237" as scoped to the gate rather than to every path that lands in this watchdog.