Uh oh!
There was an error while loading. Please reload this page.
fix(web): stop a sent image message reporting "attachment not found" - #151
Merged
Conversation
chore: promote expbkmain to bkmain (agent views in chat)
chore: promote expbkmain to bkmain (Mattermost link badge + agent view fixes)
Sending a message with an image on bkt3 could show a red banner reading "Attachment 'image.png' cannot be sent: attachment not found (removed or expired)" on a turn that had, in fact, started and was already running. Since upstream pingdotgg#8048 the web client uploads an image before sending and references it by a `pending-…` id, then releases that upload as soon as the server acknowledges the turn. The fork's durable outbox can dispatch the same queued turn twice: its replay loop is gated on a send state that clears on the server's *projection* acknowledgement, which lands before the RPC reply removes the queue row. The second dispatch is normalized before any commandId dedup, finds the pending upload already released, and fails — marking a live turn as failed. The outbox's own comment ("a failed local deletion leaves a harmless duplicate that the server will deduplicate by commandId") was true before uploads existed. It is not any more, so the durable send path now records which turns it has on the wire and which it has delivered. The replay loop skips the first and discards the second: a row left in place would latch the composer as permanently busy. Mobile is unaffected — it still queues images as inline data urls. Written by Claude Opus 5 in T3 Code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: unavailable · PR result: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Sending a message with an image on bkt3 shows a red banner reading
on a turn that has in fact started and is already running. Reported on bkmain today; the claimed attachment file for that turn is on disk, so the message was delivered and the banner is describing a second, failed dispatch of the same turn.
The cause
Since upstream pingdotgg#8048 the web client uploads an image before sending and references it by a
pending-…id, then releases that upload as soon as the server acknowledges the turn (releaseAttachmentUploads).The fork's durable outbox can put the same queued turn on the wire twice:
isLocalSendBusy, which clears the moment the server's projection acknowledgement shows the new user message (hasServerAcknowledgedLocalDispatch) — that lands before the RPC reply removes the queue row;dispatchPersistedOutboxItemdeliberately tolerates a failed local removal, on the recorded assumption that "a failed local deletion leaves a harmless duplicate that the server will deduplicate by commandId".That assumption held before uploads existed. It does not now: the duplicate is normalized in
normalizeDispatchCommandbefore any commandId dedup, finds the pending upload already released, and fails — so a live turn is marked failed and gets a Retry/Edit banner.The fix
The durable send path (
startThreadTurnDurably) now records which turns it has on the wire and which it has delivered, and the replay loop consults that record:deriveOutboxSendGate;The selection is a pure function in the fork-owned outbox module, so the call site inside
ChatView.tsxshrinks rather than grows.Surfaces
toQueuedResendAttachments), so its replays carry their own bytes.Verifying
vp test run packages/client-runtime/src/outbox/— 20 passed, 10 of them new indelivery.test.tscovering in-flight/delivered/rejected, identity and environment scoping, the bounded history, oldest-first ordering, and the legacy rows that omit an identity key.vp run --filter @t3tools/web --filter @t3tools/client-runtime typecheck— clean.apps/mobile/src/state/thread-outbox.test.tsandapps/web/src/components/ChatView.logic.test.ts— unchanged and passing.No UI was added or restyled, so there are no before/after images: the visible change is that a banner stops appearing.
Written by Claude Opus 5 in T3 Code.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.