Skip to content

fix(history): treat mid-turn absorption as delivery, not withdrawal - #14

Open
Antisophy wants to merge 1 commit into
CyberShadow:masterfrom
Antisophy:fix/midturn-absorption-delivery
Open

fix(history): treat mid-turn absorption as delivery, not withdrawal#14
Antisophy wants to merge 1 commit into
CyberShadow:masterfrom
Antisophy:fix/midturn-absorption-delivery

Conversation

@Antisophy

@Antisophy Antisophy commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Recent Claude CLIs absorb queued messages into the running turn at a tool boundary instead of holding them for the next one. The queue entry is removed after delivery, and the delivery itself is recorded as a queued_command attachment rather than as a user line: the CLI's own absorption path removes the entry only once it has been folded into the running request, and marks each folded command's lifecycle as started. The queue-primary pipeline read every remove as "removed without being consumed", so absorbed messages were badged "not delivered" while the model demonstrably received them, and the delivery record was invisible because attachment lines were never translated.

This PR translates queued_command attachments into user messages and pairs each one strictly with a remove. The pair's records land in either order, so an attachment against a still-queued entry is held until the entry's fate is known: a remove completes the absorption and releases it; a dequeue discards it, since in that flow the echo carries the message and recent CLIs write the attachment as presentation only; one matching nothing renders nothing. An entry removed with no delivery record keeps the not-delivered outcome, so a genuine queue clear still reports honestly.

The delivery record becomes the visible message, so it anchors checkpoints like any user line, and undo truncation takes its queue records with it, matched by the text they carry because the enqueue is not adjacent to the delivery (the turn's own output sits between them). An absorbed delivery carries no file checkpoint, as there is no user record for the CLI to checkpoint at, so its boundary offers conversation-only undo instead of a file revert the CLI would reject with "not a user message in this session".

Covered by a replay unit test exercising both pairing orders, the dequeue-flow attachment, and an undelivered queue clear, plus reducer tests for the provisional handover. Full check set run; a handful of contention flakes passed on serial retry.

(Rebased onto 50cd0f9; the previously stacked undo fix is superseded by it and dropped.)

Update: real-world replay surfaced two hardening requirements now folded in. Queue records are matched resiliently rather than by content text alone (the CLI omits content for array-valued messages and texts can repeat): exact match, then empty-to-empty, and a remove finally settles the oldest unsettled entry. Echo pairing likewise prefers the awaiting entry whose text the echo carries, and a joined echo (several entries drained under one dequeue record, an older CLI behavior still present in long-lived sessions) settles every queued entry whose text appears in it. Without these, one stranded entry shifts every later pairing behind by one, and each turn opener renders twice after reload. The replay unit test covers the array-message flow and the pairing after it.

@Antisophy
Antisophy force-pushed the fix/midturn-absorption-delivery branch from d6153fc to 880e2c3 Compare August 16, 2026 17:31
Recent Claude CLIs absorb queued messages into the running turn at a
tool boundary instead of holding them for the next one: the entry is
removed from the queue *after* delivery, and the delivery is recorded
as a queued_command attachment rather than as a user line. Every remove
was reported as consumed_as "removed", so messages the model
demonstrably received were badged "not delivered", and the delivery
itself was invisible because attachment records were never translated.

Translate queued_command attachments into user messages and pair each
one strictly with a remove. The pair's records land in either order, so
an attachment against a still-queued entry is held until the entry's
fate is known: a remove completes the absorption and releases it, a
dequeue discards it (the echo carries the message in that flow, where
recent CLIs also write the attachment as presentation), and one
matching nothing renders nothing. An entry removed with no delivery
record keeps the not-delivered outcome, so a genuine queue clear still
reports honestly.

Queue records are matched resiliently, because content is an unreliable
key: the CLI omits it entirely for array-valued messages and texts can
repeat. Matching prefers an exact text match, then empty-to-empty (an
empty search names an array message, whose entry is stored empty too),
and a remove finally settles the oldest unsettled entry, since the
queue really did remove one. Echo pairing follows the same principle:
dequeue records do not map enqueues one to one (the CLI can drain
several entries under one dequeue, joining their texts into one echo),
so the echo prefers the awaiting entry whose text it carries and a
joined echo settles every queued entry whose text appears in it. Any
entry left permanently unsettled would shift every later pairing behind
by one, confirming each echo against the previous message's provisional
and rendering every turn opener twice after reload.

The delivery record is now the visible message, so it anchors
checkpoints like any user line, and undo truncation takes its queue
records with it, matched by the text they carry since the enqueue is
not adjacent to the delivery. An absorbed delivery carries no file
checkpoint (there is no user record for the CLI to checkpoint at), so
its boundary offers conversation-only undo instead of a file revert
the CLI would reject.
@Antisophy
Antisophy force-pushed the fix/midturn-absorption-delivery branch from 880e2c3 to 8aaef2c Compare August 16, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant