Skip to content

cmux NEXT: an upload holds its Send's gesture ticket, so a file of any size still sends - #25810

Merged
teamleaderleo merged 3 commits into
feat-cmux-nextfrom
upload-send-ticket
Oct 27, 2026
Merged

teamleaderleo merged 3 commits into
feat-cmux-nextfrom
upload-send-ticket

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Leo, 2026-10-26: "why the FUCK SHOULD THERE BE A FUCKING SIZE CAP ANYWAYS JUST FUCKING / I ALREADY SAID WE NEED ARBITARRILY LARGE FILES OF ANY TYPE"

This is the send-ticket contract the composer lane's no-cap upload PRs (#25730 and the native uploads.begin foundation) build on. It answers the review finding that held them: a real Send's gesture is gone after 30 s, an arbitrarily large upload takes longer, uploads.begin carried no promptId and no ticket, and a longer generic gesture TTL is not acceptable.

Change

A small transaction capability on the host's gesture store (AgentPaneUserGestures). No gesture or ticket lifetime changes.

  1. Reserve at the Send. The page reserves the send ticket with the existing call, now able to bind a chat: transport.gesture {intent: {method: "session/prompt", params: {promptId, sessionId}}}. sessionId binds the ticket and is not part of the pick (AgentPaneGestureIntent.bindingKeys). A chat that is not this pane's gets no ticket.
  2. uploads.begin carries {sessionId, promptId, ticket} (AgentPaneUploadSendTicket(beginParams:); any of them missing or empty refuses the upload).
  3. The host checks and holds. AgentPaneTransport.holdSendTicket(_:upload:sessionId:promptId:): the transport is connected and the chat is the pane's. AgentPaneUserGestures.holdForUpload then checks that the ticket is this connection's, bound to exactly that chat and that promptId, and still fresh. While any of its uploads is in progress, the ticket's clock stands still. The suspension holds only while bytes move: an upload that reports no progress (uploadProgressed) for uploadStallLimit (60 s) ends the hold and the ticket. The check is lazy, with no timer.
  4. Cleanup. Finish and cancel call releaseUpload. When the last upload is released, the clock runs again from where it stopped. A newer Send's ticket (same slot, same connection), a prompt that goes out (the existing clearTickets after a sent prompt), transport.gesture.release and the connection's end each drop the ticket. That covers close(connection:), a daemon-side close (new: clearTickets(connection:)) and a reconnect. Dropping a ticket reports the uploads that held it through onUploadHoldsEnded, so the uploader cancels them.
  5. Exact redemption. The prompt with that promptId, to that chat (redeem(..., sessionId:)), redeems the ticket once, and only after every upload released it. A prompt sent while an upload still holds the ticket spends it and is refused.

Not in this PR: the uploads.begin page op, the uploader and the webview's reserve at Send. Those are the composer lane's foundation PR, which calls holdSendTicket, uploadProgressed per acknowledged frame, releaseUpload at finish and cancel, and sets onUploadHoldsEnded. #25774's section 1b should add promptId and ticket to the uploads.begin params; that is the docs PR owner's change.

Risk: high tier (relay gesture policy). Contained: a ticket with no sessionId binding behaves exactly as before, except that it now also ends with its connection (it could not be redeemed on another connection anyway).

Tests

AgentPaneUploadSendTicketTests (CmuxNextAgentPaneTests):

  • anUploadLongerThanTheTicketStillSends: two uploads held for 1200 s and 3600 s, past the 30 s gesture and the 600 s ticket lifetime. The prompt then redeems the ticket. Control: the same wait without a hold expires it.
  • aPromptWhoseUploadTookAnHourGoesOutAndOnlyOnce: end to end through AgentPaneTransport on the stand-in acpmux. With no live gesture, the prompt goes out after an hour-long held upload, and a second prompt with the same ticket is transport.gesture_required.
  • anExpiredReusedOrForeignTicketHoldsNoUpload: another connection, chat or prompt, a forged ticket, one upload held twice, a ticket with no chat binding, a redeemed ticket and an expired ticket are all refused.
  • onlyItsOwnPromptInItsOwnChatRedeemsItOnceItsUploadsEnded, aStalledUploadEndsItsHold, oneStalledUploadEndsTheHoldEvenBesideAnActiveOne (each upload has its own stall clock).
  • Cleanup: finishOrCancelReleasesTheHoldAndTheClockRunsAgain, replacementReleaseAndConnectionEndDropTheHolds (newer Send, release or prompt sent, connection end), and theConnectionsEndDropsItsHolds (through transport.close).
  • Intent and params parsing: theIntentBindsASendTicketToItsChatOnly, theBeginParamsCarryTheTicketPromptAndChat.

Observed / Not run

  • Fleet (cmux-ci run --class light --script scripts/ci/package-test-lane.sh ... --label suites, aws-m4pro-4). At head 03a324d (step 6fb2da11d4a80b6fe991b533): AgentPaneUploadSendTicketTests (11 tests) plus 10 neighbouring gesture and relay suites (RelayedAllow, SlowStartPrompt, RemoteAcpmuxWireAnswer, SendPickedFolderPrompt, AskAnswer, UnsentPrompt, FullAccessRelay, HarnessEnableRelay, SecretRelay, PostedClick), all 11 suites passed.
  • At 9b6b4c9 (step 8c8558af): anUploadLongerThanTheTicketStillSends failed. The test held two uploads but reported progress for only one, and the new per-upload stall clock ended the hold, as designed. Fixed in the test (03a324d).
  • Review: Codex gpt-6-astra xhigh (read-only). BLOCK at 9dc75be (a shared stall clock; unbound prompt tickets), then SHIP at 9b6b4c9 and at 03a324d.
  • Not run: the full CmuxNext suite, and any app build or capture. Nothing calls these APIs yet; the foundation PR wires them.

No capture: host-side gesture policy with no UI; nothing on screen changes.

Changelog

none

Agent session: agent session

🤖 Generated with Claude Code

…y size still sends

The person's Send reserves one ticket for its prompt and chat
(transport.gesture {session/prompt, promptId, sessionId}). Each uploads.begin
carries {sessionId, promptId, ticket}; the host checks the ticket is this
connection's, for exactly that chat and prompt, and fresh, then holds it: the
ticket's clock stands still while any of its uploads makes progress (a stall
past 60 s ends the hold). Finish and cancel release the hold; a newer Send, a
prompt that went out, the page's release and the connection's end drop the
ticket and report the uploads that held it. The prompt redeems it once, only
to that chat and only after every upload released it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-12c9da9c364ba148511a
@teamleaderleo teamleaderleo added the risk:high Risk tier (docs/ci/risk-tiers.md) label Oct 27, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-379m1 created for this PR by the cmux coordinator.

@blacksmith-sh

blacksmith-sh Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Found 6 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp9nqm6ucq/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmpdzaub6vz/summary-missing.json (live):/summary-missing.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Review (Codex): progress of one upload, or a new upload, refreshed a shared
progress time, so a stalled upload could keep the ticket alive. Each upload now
keeps its own last progress, and any one stalled ends the hold. The redeem doc
says why an unbound prompt ticket (trust or harness-switch hold) stays as it was:
it never holds an upload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-12c9da9c364ba148511a
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 03a324d: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr25810 -f ref=upload-send-ticket -f platform=macos -f backend_mode=local

red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on c5dd46c to compare; do not merge until green; see lane-rules

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (Codex gpt-6-astra xhigh, read-only, independent): SHIP at 9b6b4c9

First pass at 9dc75be BLOCKED on two findings: a shared stall clock (fixed in 9b6b4c9: each upload has its own) and unbound prompt tickets (the existing trust and harness-switch hold, unchanged; an unbound ticket can never hold an upload). The re-review of the delta found nothing.

Agent session: agent session

…holds

With a stall clock per upload, the test's second upload, held but silent for
twenty minutes, rightly ended the hold. Both now report progress while they run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-12c9da9c364ba148511a
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (Codex gpt-6-astra xhigh, read-only, independent): SHIP at 03a324d

The delta from 9b6b4c9 is test-only: the long-upload test now reports progress for both uploads it holds, which the per-upload stall clock requires.

Agent session: agent session

@teamleaderleo
teamleaderleo merged commit 588918a into feat-cmux-next Oct 27, 2026
139 of 145 checks passed
@teamleaderleo
teamleaderleo deleted the upload-send-ticket branch October 27, 2026 02:25
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

teamleaderleo added a commit that referenced this pull request Oct 31, 2026
…ture

chat.upload.begin may carry the Send's {sessionId, promptId, ticket}.
Such a begin holds that ticket on the transport (holdSendTicket, the
holdForUpload contract #25810 built for uploads.begin) instead of
spending the person's one gesture credit, so the prompt it was uploaded
for still goes: it redeems the ticket once every upload holding it has
finished or been cancelled. A ticket of another prompt or chat, or a
forged one, is refused before the stager. Chunks report progress (a
stalled or replaced hold cancels its stage); finish and cancel release
the hold. A begin without the three fields is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-0fec927bf7d7f6f85883

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 03a324d4 Deployed Oct 27, 2026 by teamleaderleo via wait for same-key tree publisher #61379
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant