SendMessage (cross-session/teammate) silently discards queued messages to a busy session: `queue-operation: remove` instead of `dequeue`
What's Wrong?
A message delivered to a busy Claude Code session via the cross-session/teammate messaging queue is sometimes accepted into that session's internal queue and then discarded — the transcript records a queue-operation: remove event roughly 117ms after enqueue, instead of the dequeue event that precedes every successful delivery. The message never reaches the recipient (no corresponding user-facing record is written) and no error is surfaced to the sender.
What Should Happen?
An accepted message should either be delivered to the recipient (dequeue, followed by a user-facing record), or its loss should be surfaced to the sender. Silently dropping a message that was already accepted into the queue leaves both sides believing it was delivered.
Error Messages/Logs
There is no error message — that is central to the report. The only signal is the queue-operation pattern in the recipient's transcript:
enqueue <message id>
remove <message id> # ~117ms later, instead of dequeue
A successful delivery instead shows:
enqueue <message id>
dequeue <message id>
<user-facing record>
Evidence gathered:
- Each affected message's identifier appears in the recipient's transcript first as an
enqueue-type queue-operation, then ~117ms later asremoverather thandequeue. - We cross-checked message identifiers: none of the identifiers that were
removed ever appear against a subsequent user-facing record. That rules out the message being consumed by some other path under a different record shape — it is genuinely lost, not just recorded oddly. - Observed on two separate sessions: 17 messages enqueued, 8 of them removed without delivery. That sample is enough to say the behavior is systematic (it isn't a one-off), but is not large enough for us to state a loss rate, and we are not claiming one.
Steps to Reproduce
We cannot give deterministic steps, and would rather say so than offer steps that don't reliably trigger it. This is observational evidence gathered from transcript inspection across two real working sessions, not an isolated test case.
How it was observed:
- A local bridge (an MCP server, not a published/named plugin) delivers asynchronous notifications into a session that is already mid-turn — it routinely targets a session while it is busy, by design, rather than waiting for it to go idle.
- Mechanically, the bridge writes a JSON-RPC notification to the target session's MCP stream, and only replies success to its own caller after that write completes — so the bridge's own delivery step genuinely finished before the notification reached Claude Code.
- Inspecting the recipient session's transcript afterwards shows some of those messages enqueued and then removed without ever being dequeued or surfaced.
The discard we're reporting only manifests on a busy recipient, so this is why our traffic hits that condition so consistently, and why we encounter the discard often enough to have noticed it.
We're happy to help characterize it further (e.g. by instrumenting more sessions) if that's useful, but wanted to raise it now given the silent-data-loss nature of the bug.
Is this a regression?
Don't know — we have not tested earlier versions for this behavior, so we can't say whether it ever worked differently.
Claude Code Version
v2.1.236 / v2.1.237
Operating System
Linux
Additional Information
Scope caveat — MCP-originated traffic: We have not tested whether this also affects messages from a non-MCP sender (e.g. the built-in teammate SendMessage tool, or /goal check-ins) — it may be specific to MCP-originated queue traffic. Please treat this report as scoped to that path unless you can confirm it's broader.
What we have not determined: what specifically triggers the removal instead of normal dequeue/delivery. In both observed cases the removal landed while the recipient session was mid-tool-round (a tool call in flight), and both successful deliveries landed in the gap between an assistant text record and the next record — but we have not proven this correlation and are not confident it's causal; we deliberately did not disassemble the client binary to chase it further, so please treat this as a lead for maintainers, not a finding.
Not the same as the documented 2.1.236 change: v2.1.236's release notes state SendMessage now refuses further messages to a session up front once a rapid burst would exceed what that session's inbox accepts, instead of reporting them sent while they were dropped. We checked — that entry describes rejecting a call before it's accepted, with the refusal presumably visible to the caller at call time. What we're reporting is different in shape: the message is accepted into the queue, and is lost silently afterward, well after any burst-refusal check would have run. We don't believe the documented change accounts for this, but are flagging the distinction explicitly in case it's related at a level we can't see from the outside.
Happy to share transcript excerpts (redacted) showing the enqueue/remove pattern if useful.
3 Comments
Closing this — 2.1.238 ships two cross-session messaging changes that appear to address it:
The second one matches the shape reported here: the message is accepted, then lost, and the sender is told nothing.
It also offers a plausible explanation for the part we could not account for. We saw a
removeoperation on the recipient's queue rather than adequeue, with no reason we could determine from the outside, and it correlated with the recipient being busy. "Rate limit or full queue" fits both observations.To be clear about where we stand: we have not re-tested on 2.1.238. This is being closed on the strength of the changelog entries, not on a verified fix. If we see the behaviour again on 2.1.238 or later, we'll open a new report with fresh evidence.
Thanks for the quick turnaround on this.
Re-triage notice — please treat the evidence in this report as provisional.
We are re-examining the data behind the original report, and we have reason to believe our analysis method was flawed in a way that could account for the entire reported symptom without any message having been lost.
The error is in this step from the report body:
That cross-check looked for evidence of delivery in a single record type. On the path our local tooling uses, a delivered message is recorded under a different record type than the one we searched. If that holds, the check could only ever have returned "no match" — whether the message was delivered or lost — so it did not rule out what we claimed it ruled out, and the
removewe read as a discard may simply be the queue entry being consumed on successful delivery.Two things this does not say:
We will follow up once it completes, and if the original symptom turns out to be real after all, we will re-file with evidence that actually distinguishes delivery from loss.
Apologies for the noise if this report rested on an analysis error of our own. Getting that right was our burden, and the flawed step was precisely the one we claimed had ruled the alternative out.
Follow-up, as promised: the re-triage is complete, and this report was wrong. Our evidence never supported it.
A second analysis was run deliberately without sight of the first one's reasoning or its conclusion. It re-derived the question from the raw records and arrived at the same result independently: the operation we read as a discard is the queue entry being consumed on delivery, not dropped. Every message that was enqueued was delivered, in the same session, under a record type our original cross-check never examined.
Raw counts from that re-derivation, over our full local corpus:
removed; all 206 have a matching delivery record.removeoperations equals the number of delivery records. Not one session shows a shortfall.Delivery was confirmed three independent ways: by record structure, by file ordering (in all 206 cases the delivery record is written after the
remove, never before), and behaviourally — reading what the recipient did next and finding it acting on the content of the very message we had counted as lost.The flawed check
From the report body:
That check searched a single record type for evidence of delivery, while deliveries on this path are recorded under a different one. It could only ever return "no match" — whether the message arrived or was lost. It did not rule out the alternative; it was structurally incapable of detecting it, and the sentence claiming otherwise was the least justified one in the report.
The "17 enqueued, 8 removed" we cited as systematic loss was 8 successful deliveries.
Scope, restated
This covers only the transport our own local tooling uses (an MCP server, not a published plugin). We have no visibility into the cross-session mailbox path the 2.1.238 changelog entries describe, and nothing here bears on it. This is not a claim that the behaviour those entries fixed was unreal — only that we never had evidence of it.
One open thread, disclosed rather than reported
The same verification surfaced a different observation: messages accepted by our own local server that leave no trace on the recipient side. We are deliberately not putting this forward as a lead. The method that found it has not been characterised for false positives, and we cannot say where such a message would go missing — including whether Claude Code ever received it at all. We are investigating it now, and will file separately only if it survives scrutiny, with evidence that actually distinguishes delivery from loss.
Given how this report turned out, we would rather under-claim that one until we can prove which side of the boundary it falls on.
Thanks for your patience while we sorted this out.