cmux NEXT composer: a send waits for its uploading files; a failed upload retries on its own

Merged
TE
5d ago
+449-100

Leo, 2026-11-01, after pasting a 17 s mp4 into a chat on another machine and sending "here": the agent answered "Nothing came through with 'here', no image or text", the chip showed a blank file icon with Retry, and then "oh i guess mp4 couldnt get sent" and "i had to retry to get it to load???". After Retry the chip showed the video and the agent read it.

Rulebook: "Attachments take arbitrarily large files of any type; there is no size cap."

What was wrong

Two things in the composer (shared web), whatever made the first upload fail:

  1. An upload never held a send. Since #25191, a chip streaming to the chat's machine (and a chip whose upload failed) did not count for the send hold (attachmentIntake.tsx loading). So "here" went at once without the video, as composer-send-hold.test.tsx "a send made while only uploads are left goes at once" pinned.
  2. A failed upload showed Retry at once. One failure of any step (begin, a chunk, the host's hand-off to the peer) left the chip failed until the person clicked Retry.

What changed

  • A send waits for every file the person added: still reading, uploading, or whose upload failed (until its Retry makes it or its Ɨ takes it out). A file the composer refused (no images in this chat, one that won't decode) holds nothing, since Retry can't change it. The composer stays free while it waits: typing, the + menu and Ɨ all work, as #25191 wanted. A file nobody is waiting on no longer slips out of a prompt.
  • A failed upload tries again on its own (composer/uploadRetry.ts): once after 1 s, then on the person's next key or click in the page (never Return, which is the send's gesture), since a begin needs a fresh gesture. Only then does the chip show Retry. The chip keeps loading meanwhile. Its Ɨ stops a waiting try, and so does the composer's unmount.

Known limit (follow-up): a Retry made while a send is held, or the automatic try 1 s after a failure, spends the person's latest gesture (the Enter's, when it is still fresh), so the held prompt can come back refused (it stays in the composer, nothing is lost) and goes on the next Enter.

Red / green

testcommandbase (red)head (green)
composer-upload-first-send.test.tsx (5 cases)cd webviews && bun test test/composer-upload-first-send.test.tsxpendingpending
agent-pane-attach-huge-files.test.ts (case 2 now expects the held message to go with its file)browser lanependingpending

Observed / Not run

  • Pending: the runs above via cmux-ci test.
  • Not run yet: a DEV build sending a 17 s mp4 to a remote session. The first failure's cause, from the Mac's acpmux log: peer_unreachable for that lane VM ("SSH connection refused") right then, so acpmux refused the upload at once while the link reconnected. The acpmux side (an upload waits for a reconnecting link) is its own PR, lane/upload-peer-link-wait. This PR makes the composer correct whatever the failure.

risk: normal. One behavior change: a send made while a file uploads now waits for it (Ɨ sends without it).

Changelog

  • Fixed: a message sent while an attachment is still uploading waits for it and goes with it, instead of going without it; a failed upload retries on its own before asking for Retry.

Agent session: agent session

šŸ¤– Generated with Claude Code