Dispatch session collisions cause message loss and transport failures
Bug: Dispatch session collisions cause message loss and transport failures
Environment
- App: Claude Desktop (macOS)
- Date observed: 2026-03-27
- Log source:
~/Library/Logs/Claude/main.log
Summary
When using Dispatch mode (Cowork), the sessions-bridge encounters repeated session collisions where multiple cloud session IDs (CSEs) attempt to bind to the same local Dispatch parent session. This creates a rapid displacement loop that prevents messages from being delivered, causes transport initialization failures, and results in user messages being silently dropped or routed incorrectly.
The user-visible symptom is that replies typed into a Dispatch thread are either lost entirely or "passed off" into a different Copilot chat session that then fails to respond.
Reproduction
- Open a Cowork/Dispatch session with several child tasks spawned.
- Send follow-up messages in the Dispatch thread while child sessions are active.
- Observe that the reply either never arrives or surfaces in the wrong session context.
Exact trigger conditions are unclear, but the collision loop began immediately after a prior session query completed with isError=true.
Error Details
1. Session collision loop (11 collisions in ~2 minutes)
Starting at 17:03:58, pairs of CSE sessions repeatedly displace each other on the same local parent (local_ditto_6cf1aa12-...):
[error] [sessions-bridge] Session collision: cse_016UUj8Mj7XJAUsMEAWAw5hW is displacing cse_01DPeFSKvZkbvD3heTsfDWzN (both bound to local_ditto_6cf1aa12-..., displaced pendingTurns=0)
[error] [sessions-bridge] Session collision: cse_01DPeFSKvZkbvD3heTsfDWzN is displacing cse_016UUj8Mj7XJAUsMEAWAw5hW (both bound to local_ditto_6cf1aa12-..., displaced pendingTurns=0)
This pattern repeated with at least 6 different CSE IDs cycling against each other (cse_01DPeF..., cse_016UUj..., cse_01NLWFp..., cse_015zc5..., cse_01Dbz..., cse_01Qtic...).
2. Transport initialization failure during collision
A collision triggered a deleteBridgeSession which wiped the local parent while a new CSE transport was still attaching:
[warn] [sessions-bridge] Failed to post final result for cse_01DPeFSKvZkbvD3heTsfDWzN; server DELETE may 409: write() before transport initialized
[error] [sessions-bridge] Failed to connect transport for session cse_01DPeFSKvZkbvD3heTsfDWzN: transport closed during attachBridgeSession
The sequence was: collision detected -> deleteBridgeSession wipes local parent -> OAuth cache cleared -> fresh OAuth exchange started -> but transport was already closed -> attachBridgeSession fails.
3. Stale pendingTurns
Throughout the session, there are recurring warnings about stale pending turns suggesting the bridge loses track of in-flight work:
[warn] [sessions-bridge] Stale pendingTurns detected for session cse_01A2W7aptb5XBXZXwLsrc7Bx (pendingTurns=2), resetting counter and keeping transport open
[warn] [sessions-bridge] Stale pendingTurns detected for session cse_01PJkqGu6XntvdWGT7r4uG2v (pendingTurns=1), resetting counter and reconnecting transport
4. User-reported symptoms in-log
The logs capture the user's own messages describing the failure:
17:25:31— "Something is going on with your messaging system in dispatch mode. Your replies are being sent via s..."18:15:21— "You're having problems responding in this dispatch thread. I can see you kicked off a claude code se..."18:21:07— "I'm typing in a dispatch thread, but its literally just passing it off into a copilot chat and faili..."
Root Cause Hypothesis
The sessions-bridge poll API is returning duplicate or recycled work items for the same local Dispatch parent. When a new CSE arrives while an existing CSE is already bound, the bridge displaces the old one. But the displaced session re-appears in the next poll cycle, creating a ping-pong loop. This loop:
- Prevents any single transport from staying connected long enough to relay messages.
- Can trigger
deleteBridgeSessionwhich wipes the local parent and all its OAuth state, causing a cascade of re-authentication and further transport failures. - Leaves
pendingTurnsin a stale state so the bridge doesn't know whether work completed.
A possible contributing factor is the isError=true completion at 17:03:33 on the prior session (cse_01A2W7apt...), which may have left the server-side session in an ambiguous state that caused it to re-dispatch work.
Expected Behavior
- Only one CSE should be bound to a local Dispatch parent at a time.
- If the server issues a new CSE for the same parent, the old CSE should be cleanly retired (not re-polled).
- User messages sent during Dispatch should be reliably routed to the active session and never dropped.
Actual Behavior
- Multiple CSEs fight over the same local parent in a collision loop.
- The transport is destroyed and re-created repeatedly, sometimes failing to initialize.
- User messages are lost or misrouted to the wrong session type.
Suggested Investigation Areas
- sessions-bridge poll deduplication: Why does the poll API return work for sessions that were just displaced? Is there a missing acknowledgment or session-state fence?
- deleteBridgeSession race condition: The wipe-and-recreate path during a collision doesn't appear to guard against concurrent transport attachment.
- OAuth cache invalidation during collision: The collision path calls
clearing token cachewhich forces a fresh exchange; under rapid collision this could amplify latency and failure rates. - Stale pendingTurns recovery: The "reset counter" path doesn't appear to notify the server, potentially causing the server to re-dispatch the same work.
Log File
Full logs available at ~/Library/Logs/Claude/main.log on the reporter's machine. Key line ranges: 15972–16296 (collision loop), 18170–18200 (user complaint context).
13 Comments
Found 3 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Yeah this happens to me too
Still reproducing after full uninstall/reinstall of Claude Desktop. The same session collision loop is happening on the fresh install (2026-03-28 ~21:45-21:48 UTC):
New findings vs. original report:
cse_01PJkqGu6XntvdWGT7r4uG2v(a session from the pre-reinstall run) is still being polled by the server and now returns a 404 when the client tries to attach a transport.[warn] Invalid transition archived → stoppingfor the Dispatch parent session, suggesting the client is trying to operate on an already-archived session.local_ditto_6cf1aa12-...) persisted across the reinstall because it is tied to the org ID, not the local app installation. The server continues to dispatch stale work items for sessions that will never be serviceable, causing the collision loop to resume immediately on any fresh install.This is a server-side bug - no client-side workaround will fix it.
Update 3: Collision loop is resolved, but Dispatch UI still not rendering responses.
After deleting the stale
local_ditto_6cf1aa12-...session files from/~/Library/Application Support/Claude/local-agent-mode-sessions/.../agent/, the session collision loop stopped. The new Dispatch session (cse_01YAYXMajbLXoy2XpCV78SDi) connects cleanly and completes queries withisError=false.However, the Dispatch thread UI still does not render any responses. The user can send messages (they show as sent with a "Read" checkmark), but no reply ever appears in the thread.
From
claude.ai-web.log:And from
main.log:This looks like the UI renderer is trying to look up data associated with the old Dispatch parent session that no longer exists, hitting "Not found" in the React query cache, and silently failing to render.
So there are actually two bugs here:
local_ditto_*session files..includes. There is no way to start a fresh Dispatch thread from the UI to work around this.Additional Finding: Dispatch agent spawned with UI presentation tools explicitly disallowed
After further investigation into the Dispatch UI responsiveness issue, I discovered a separate bug in the Dispatch spawn configuration that prevents the agent from rendering results in the Cowork UI — even when the backend successfully completes the task.
The Problem
In
cowork_vm_node.log, the Dispatch agent's spawn command includesmcp__cowork__present_filesin both--allowedToolsand--disallowedTools:Since
--disallowedToolstakes precedence, the following tools are effectively blocked for Dispatch sessions:mcp__cowork__present_filesmcp__cowork__create_artifactmcp__cowork__update_artifactThese are the exact tools the agent needs to render file outputs and artifacts back into the Cowork UI.
Observed Behavior
hello.html(confirmed via file system and Desktop panel in UI)present_filestool call was blockedImpact
This is a separate issue from the session collision loop documented above. Even after resolving the session collisions (via "Disconnect all and retry" or clearing
bridge-state.json), the Dispatch agent still cannot present results to the user because the presentation tools are disallowed in the spawn configuration.Environment
Diagnostic Update: Backend succeeds, UI fails to render responses
Further investigation via Cowork session analyzing
main.login real-time confirms the issue is a client-side UI rendering bug, not a backend failure.Evidence
After a full quit + relaunch + sign-out/sign-in cycle:
409registration conflict ("A Cowork agent is already registered on [hostname]") that appeared on previous launches did not recur after the restart.dispatchserver (5 tools). No connection errors.main.logshows:``
``[sessions-bridge] Received user message for session cse_016pGmAszALq5imfL17yssmc: "You're back!..."
[Lifecycle] Session local_ditto_*: idle → initializing → running
[Result] Turn succeeded for session local_ditto_*
[sessions-bridge] Query completed for session cse_016pGmAszALq5imfL17yssmc (pendingTurns=0, isError=false)
[CycleHealth] Healthy cycle: { cycle_health: 'healthy', had_first_response: true, seconds_to_outcome: 12 }
had_first_response: trueandisError=false, no response renders in the Dispatch panel. The user sees their sent message and silence.Key Distinction
This is not the session collision issue from the original report. The 409 conflict was absent on this launch. The sessions-bridge connected cleanly, the Dispatch parent agent processed every message to completion, and CycleHealth reported all cycles as healthy. The disconnect is between the sessions-bridge reporting
Query completedand the Dispatch UI panel actually rendering the response.Secondary issues observed
coworkd.log: DNS resolution failures (Cannot read TLS response from mitm'd server dial tcp: lookup api.anthropic.com on 127.0.0.53:53: connection refused) during VM startup — resolved after recoveryswift.log: Notifications are denied (Notifications are not allowed for this application) — cosmetic but contributes to apparent unresponsivenessmain.logat 22:53:20: Sentry errorProcessTransport is not ready for writing— may indicate transport instability between app and VMEnvironment
Follow-up: Dispatch agent IS generating full responses — they vanish before rendering
Examined the Dispatch parent session's
audit.jsonltranscript directly to see what the agent thinks it responded with. The agent is generating complete, substantive responses to every message — they're just not making it to the UI.What the agent actually generated (from
audit.jsonl)For each user message sent in the Dispatch UI, the parent agent (
local_ditto_*) produced full responses:claudecleanreinstall.md, fetched the GitHub issue via WebFetch, and wrote a multi-paragraph analysis summarizing all three bugs identified in the issue. Full tool use chain:Read→Bash→WebFetch→ detailed response.The gap
Every one of these responses shows up in
audit.jsonlas completed assistant turns. Themain.logconfirms each with[Result] Turn succeeded,Query completed (pendingTurns=0, isError=false), and[CycleHealth] Healthy cycle: { had_first_response: true }.The responses are being generated, serialized to the audit log, and reported as delivered to the sessions-bridge. They disappear somewhere between
sessions-bridgereportingQuery completedand the Dispatch UI panel rendering them. Only the very first response after a fresh app restart rendered visibly.Implication
This is definitively a client-side rendering bug in the Dispatch UI panel, not a backend, transport, or agent issue. The full agent pipeline (message receipt → processing → tool use → response generation → session completion) works correctly every time.
Root Cause Found: Dispatch agent stops using
SendUserMessageafter first turnTraced the exact mechanism by examining the Dispatch parent session's
audit.jsonltranscript line by line.The mechanism
The Dispatch parent agent (
local_ditto_*) hasSendUserMessagein its--allowedToolsand uses it correctly for its very first response:This first message rendered successfully in the Dispatch UI.
For every subsequent user message, the agent responds with plain text blocks instead of
SendUserMessagetool calls:The agent generated thoughtful, complete responses — including multi-tool investigation chains — but never routed them through
SendUserMessage, so they never reached the Dispatch UI. The sessions-bridge reportsQuery completed (isError=false)because the turn did complete from the backend's perspective.Why it happens
The
SendUserMessagetool is available (--allowedToolsincludes it), so this appears to be a model/system-prompt behavior issue: after the first turn, the agent falls back to plain text assistant responses instead of callingSendUserMessage. The Dispatch UI apparently only renders content delivered viaSendUserMessage, not raw assistant text blocks.The
present_filesconflict (separate issue, confirmed still present after update)Also confirmed that
mcp__cowork__present_filesstill appears in both--allowedToolsand--disallowedToolsin the spawn config after today's app update (SDK 2.1.85). The disallowed list wins, blocking file/artifact presentation:Additionally,
main.logshowsBridge session: disabling renderer-dependent toolsevery time the Dispatch parent initializes — this is the code path addingpresent_fileset al. to the disallowed list without checking for conflicts with the allowed list.Summary
There are two distinct bugs causing Dispatch UI silence:
SendUserMessageafter its first turn, falling back to plain assistant text that the UI doesn't display. Fix would be either enforcingSendUserMessageusage in the Dispatch system prompt, or having the UI render raw assistant text blocks.present_files,create_artifact, andupdate_artifactare blocked by the renderer-dependent tools disabling code, preventing file and artifact presentation even when the agent tries to use them.No user-facing settings can work around either issue.
Workaround Found + Full Root Cause Chain
Workaround (confirmed working)
Telling the Dispatch agent directly to use
SendUserMessagerestores visible responses:The agent acknowledged the bug and committed to using
SendUserMessagegoing forward. Responses are now rendering in the Dispatch UI.Full root cause chain
Traced by examining
cowork_vm_node.logspawn configs and the Dispatch parent'saudit.jsonltranscript:SendUserMessageis only available to the Dispatch parent (Bridge session) — confirmed by comparing--allowedToolsacross all 49 spawned sessions in this log. Child sessions (Cowork, Dispatch tasks) never have it. This is by design.SendUserMessageexactly once — on its very first response. Every subsequent response (7+ messages) was generated as plain assistant text blocks, which the Dispatch UI silently drops.--resumeflag locks in the bad pattern — the Dispatch parent always spawns with--resume <same-cli-session-id>, replaying the full transcript. Once the model responded with plain text once, every subsequent resume loads that history, and the model mimics its own prior behavior. The pattern is self-reinforcing.local-agent-mode-sessions/(containing the transcript) andbridge-state.json(binding to the cloud session) survive or get re-bound to the same session state. The accumulated plain-text response history persists across reinstalls.present_filesconflict is separate but still present —mcp__cowork__present_filesappears in both--allowedToolsand--disallowedToolsin the spawn config, with disallowed winning. This blocks file/artifact presentation but is not the cause of text message silence.Why this may be user-specific
The core bugs (renderer-dependent tools disabling,
present_filesconflict) appear to be universal in the codebase. But the symptom — persistent Dispatch silence — likely requires a trigger: the model needs to respond with plain text instead ofSendUserMessagejust once, and then the--resumeflag makes it permanent. Different users might not hit this depending on their initial interaction pattern, conversation length, or model sampling.Suggested fixes (in order of impact)
SendUserMessagein the Dispatch parent system prompt — the system prompt should mandate that all user-facing responses go throughSendUserMessage, not plain textpresent_filesallowed/disallowed conflict — remove it from the disallowed list for Bridge sessions, or check for conflicts before applying the renderer-dependent tools blocklist--resumeindefinitely — or periodically resetting the Dispatch parent transcript to prevent behavioral driftYour debugging skill is extremely high! Your debug work is incredibly valuable.
A user in #40281 found one root cause for Dispatch responses not rendering: the missing --brief CLI flag prevents SendUserMessage from being registered. However, your issue also involves a separate problem (TypeError on dispatch_agent_name being null / session collision loop) which may be an independent bug. My Claude is not entirely sure the --brief fix alone would resolve your case. You should check issue #40281 for the full details.
Closing for now — inactive for too long. Please open a new issue if this is still relevant.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.