Two Invitations That Went Nowhere
On 2026-09-29 Coop wrote two invitations for todo #273 into an existing Claude Desktop chat. Both socket writes succeeded. Neither ever reached the chat's transcript. The chat was not busy; it was idle at both sends. What it was, since earlier that afternoon, was running in bypassPermissions. All 24 earlier successful Coop peer entries had landed in chats running in auto.
Here is what Claude Code 2.1.284 does, as Coop's probe later measured it. Every inbound frame is judged before it is queued. When no crossSessionInbound value applies, the verdict comes from the two sessions' permission classes: a receiver that bypasses permission prompts holds a frame whose sender does not say it bypasses too. Coop's frame asserts no permission class at all. So the frame was held for approval until dialogExpiry (five minutes by default), then dropped as expired. A Desktop chat cannot even show the approval dialog. Nothing is written to the transcript. The only signals are an event on the host's SDK stream and a status receipt sent back to the frame's from address, and cwkCoop is a label, not an address anyone can answer.
An isolated probe with the Desktop-bundled executable and a 60-second expiry reproduced it exactly: a default receiver delivered and echoed; a bypass receiver held the frame at once and dropped it at sixty seconds; a bypass receiver with crossSessionInbound: accept delivered and echoed.
The Fix That Was Not Taken
There was a one-line "fix": have Coop's frame claim a bypassing sender, and the gate opens. Coop does not do it. Asserting a permission mode Coop does not have would pass the gate by misstating who is knocking, and a peer frame is never Dad's authorization to begin with. Dad chose the documented lever instead: crossSessionInbound: "accept" in office's user settings, plus manual recovery for anything already lost. The engine gained one new transition for that recovery, and only one.
Predict the Receiver's Verdict, Record It at Send
Settings fix the chat they apply to. They do not fix the next Mac, the next chat started with its own flags, or the receiver whose repository decides to tighten things. So at checkpoint time, just before sending, inbound_policy() predicts the receiver's verdict and stores it with the delivery:
| Source | Role in the prediction |
|---|---|
| Managed settings | First explicit crossSessionInbound wins |
The host's --settings flag on the chat's process | Next |
| User settings | Next |
| Repository and local settings | Can only tighten: a hold or refuse there raises the verdict, never lowers it |
| No explicit value anywhere | Permission-mode parity: a bypassPermissions receiver means hold, any other recorded mode means accept |
| Anything unreadable or unrecognized | unknown, which never expires a delivery |
Three Ways to Be Not Delivered, and Only Three
While a send is uncertain, the adapter asks delivery_verdict() whether the receiver's own policy proves the frame can no longer arrive. It answers with exactly one of three reasons, or with nothing:
- refused-by-receiver-policy: the predicted verdict was
refuse. That drops at once. - held-for-approval-then-expired: a parity hold, and now is past send time plus
dialogExpiryplus a 60-second grace for the file append. An explicitholdset in a settings file never expires; it stays watched, because anacceptmay release it later. - receiver-idle-without-entry: the verdict was
accept, at least 60 seconds have passed, the registry says the chat is idle, and a fresh read of the transcript does not contain the message id. An accepting chat that is idle would have taken the frame immediately; idle without the entry means it was dropped at the inbox.
Only then does the engine's delivery command move the outbox row from uncertain to expired, and it refuses unless the row really was an uncertain send and the evidence carries a reason. The workspace shows the reason in plain words and a Send again button.
Nothing Resends on Its Own
Send again is Engine._resend, and it is narrow on purpose. The row must be expired, meaning proven undelivered rather than merely quiet. Its kind must be an invitation whose dispatch is still invited, or a work envelope whose dispatch still has an active binding. And it works once: the old row records the new id as resent_as, and a second press answers This delivery was already sent again. The trigger is Dad's press, or the chief's resend decision since the chief became Dad's proxy on 2026-09-30, and the chief's version has the same narrow conditions. No timer, adapter or retry loop ever creates a second attempt. Silence never proves agreement, and it never proves loss either.
The General Lesson: Delivery Semantics
Strip the vendor detail away and you have the oldest problem in distributed systems. A sender cannot know, at the moment of sending, whether the message arrived; the Two Generals cannot agree over a lossy channel no matter how many messengers they send. So you pick semantics and build to them. Coop's architecture picks these:
- At least once, deduplicated before effects. A crash after the bytes leave can cause a repeat, so receivers deduplicate by id before anything happens. Every engine command carries an idempotency key such as
accept:plus the outbox id, so the same acceptance applied twice is one acceptance. - Intent before I/O. Before the first byte, the engine row flips from
pendingtouncertainand the host queue records the same attempt. A crash anywhere afterward leaves an uncertain send, never an apparently unsent one. On restart, an uncertain row is searched for by its stable id; the code says it plainly: never infer unsent. - Unknown outcomes are reconciled before any non-idempotent retry. A new native turn is not idempotent; a second invitation might be accepted twice. So the only path to a second attempt runs through evidence of loss and a deliberate choice.