Skip to content
C.W.K.
Stream
Lesson 02 of 04 · published

Held, Dropped, Send Again

~16 min · delivery-semantics, at-least-once, idempotency, claude-code, inbound-policy

Level 0Loose Note
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

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:

SourceRole in the prediction
Managed settingsFirst explicit crossSessionInbound wins
The host's --settings flag on the chat's processNext
User settingsNext
Repository and local settingsCan only tighten: a hold or refuse there raises the verdict, never lowers it
No explicit value anywherePermission-mode parity: a bypassPermissions receiver means hold, any other recorded mode means accept
Anything unreadable or unrecognizedunknown, 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 dialogExpiry plus a 60-second grace for the file append. An explicit hold set in a settings file never expires; it stays watched, because an accept may 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 pending to uncertain and 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.
A retry is a decision, not a reflex. Automatic retry is only safe when the operation is idempotent at the receiver. When it is not, the honest states are delivered, proven lost, and unknown, and only a human, or someone holding the human's authority, turns proven lost into a second try.

Code

desktop.py: the three not-delivered verdicts (verbatim, docstring trimmed)·python
    def delivery_verdict(self,message_id,cursor,sent_at,now=None):
        """Evidence that a sent frame can no longer arrive, or None to keep watching."""
        if cursor.get('admitted') or cursor.get('native_session')!=self.session_id:
            return None
        now=time.time() if now is None else now
        policy=cursor.get('inbound') or {'verdict':'unknown'}
        base={'native_session':self.session_id,'message_id':message_id,'sent_at':sent_at,
              'observed_at':now,'transcript_offset':cursor.get('offset'),'inbound_at_send':policy}
        if policy['verdict']=='refuse':
            return {**base,'reason':'refused-by-receiver-policy'}
        if policy['verdict']=='hold':
            expiry=EXPIRY_SECONDS.get(policy.get('dialog_expiry'))
            if policy.get('source')!='permission-mode-parity' or expiry is None:
                return None
            if now>=sent_at+expiry+self.GRACE_SECONDS:
                return {**base,'reason':'held-for-approval-then-expired'}
            return None
        if policy['verdict']=='accept' and now>=sent_at+self.GRACE_SECONDS:
            value=self.registry_value() or {}
            # Read the transcript again after the idle status: a frame consumed
            # since the last observation pass is in the file before idle is.
            if value.get('status')=='idle' and not self.mentions(message_id,cursor):
                return {**base,'reason':'receiver-idle-without-entry','receiver_status':'idle'}
        return None
A minimal sketch (not Coop's code): intent before I/O, evidence before expiry, one deliberate resend·python
import sqlite3, uuid

db = sqlite3.connect(":memory:", isolation_level=None)
db.execute("CREATE TABLE outbox (id TEXT PRIMARY KEY, body TEXT, state TEXT, resent_as TEXT)")

def enqueue(body):
    oid = str(uuid.uuid4())
    db.execute("INSERT INTO outbox VALUES (?,?,'pending',NULL)", (oid, body))
    return oid

def deliver(oid, transport):
    # 1. Record the intent BEFORE the first byte leaves.
    cur = db.execute("UPDATE outbox SET state='uncertain' WHERE id=? AND state='pending'", (oid,))
    if cur.rowcount != 1:
        return "not eligible: observe the uncertain send instead"
    body = db.execute("SELECT body FROM outbox WHERE id=?", (oid,)).fetchone()[0]
    transport(oid, body)          # may crash; the row already says 'uncertain'
    return "sent; waiting for a receipt"

def observe(oid, receiver_log, verdict=None):
    # 2. Only evidence moves an uncertain send: a receipt, or a policy verdict.
    if oid in receiver_log:
        db.execute("UPDATE outbox SET state='delivered' WHERE id=?", (oid,))
    elif verdict:
        db.execute("UPDATE outbox SET state='expired' WHERE id=? AND state='uncertain'", (oid,))
    return db.execute("SELECT state FROM outbox WHERE id=?", (oid,)).fetchone()[0]

def send_again(oid):
    # 3. A human-chosen second attempt, allowed once, only after proof of loss.
    state, resent = db.execute("SELECT state, resent_as FROM outbox WHERE id=?", (oid,)).fetchone()
    if state != "expired" or resent:
        raise RuntimeError("only an undelivered send, and only once")
    body = db.execute("SELECT body FROM outbox WHERE id=?", (oid,)).fetchone()[0]
    new = enqueue(body)
    db.execute("UPDATE outbox SET resent_as=? WHERE id=?", (new, oid))
    return new

dropped = []                       # a receiver that silently drops frames
first = enqueue("invitation")
print(deliver(first, lambda i, b: None))
print(deliver(first, lambda i, b: None))        # a blind retry is refused
print(observe(first, dropped))                  # silence: still uncertain
print(observe(first, dropped, verdict="held-for-approval-then-expired"))
second = send_again(first)
print(deliver(second, lambda i, b: dropped.append(i)))
print(observe(second, dropped))
try:
    send_again(first)
except RuntimeError as e:
    print("refused:", e)

External links

Exercise

Find a retry in your own code: an HTTP client with retries, a job queue, a webhook sender. Write down whether the operation it repeats is idempotent at the receiver. If it is not, add two things: a durable record written before the request goes out (pending, then uncertain), and a reconcile step that looks the request up by its id on the receiving side before any second attempt. Run the sketch above first to see the four states move.
Hint
The test for idempotence is not "does my code handle a duplicate" but "does the receiver produce the same effect if it sees this twice". A payment, an email, a new chat turn and a new database row usually fail that test; a PUT of a full record or an upsert keyed by your own id usually pass it. If the receiver accepts a client-supplied id, send one and reuse it on retry.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.