Skip to content

acpmux: send --attach and send_message take files of any size and number, by reference - #25840

Merged
teamleaderleo merged 16 commits into
feat-cmux-nextfrom
hb-busy-accept/attach-by-reference
Oct 27, 2026
Merged

teamleaderleo merged 16 commits into
feat-cmux-nextfrom
hb-busy-accept/attach-by-reference

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"

Base feat-cmux-next. It builds on #25793 (resumable uploads, upload.diskFull with host and path, merged as e6adb7f).

After #25730 the prompt path had no cap, but acpmux send --attach and cmux-subsessions send_message attachments still refused a message: 8 MiB a file, 10 MiB and 16 files a message, and "one message is at most 15 MiB" (attach.rs).

Change

The senders (attach::blocks_for) never refuse a file for its size or their number:

  • Files that fit one message with the text go inline as before (fits_inline: at most 16 files, each at most the inline image size of 3.75 MiB, 10 MiB together, and the whole message as base64 within one JSON-RPC message). Old receivers keep working for these.
  • Otherwise each file is uploaded to the receiving chat first with _acpmux/file_put_begin/_chunk/_finish (512 KiB chunks read from disk as they go, so memory stays flat), and the prompt names it by reference (acpmux: a prompt names its files as uploads, of any size, by reference #25730). The daemon that runs the chat puts an image the agent can take inline and gives every other file, and an image too large to show, by its path. A failed upload is cancelled.
  • Before any byte is sent it checks that the uploads will be read. The local daemon must list promptUploads; if it does not, the error names its build and says to restart it. The begin carries forPrompt: true, which a peer without promptUploads refuses (upload.unsupported). So no upload is left behind for a prompt that would be refused, --no-wait included.
  • Nothing is left behind:
    • The sender sends every file's bytes before it finishes any. If a file fails, it cancels every upload it began.
    • file_put_cancel with ownerToken deletes an upload begun for a prompt whole, finished or not, and only with the matching token, here or through a peer. Any other upload is cancelled only while unfinished, as before.
    • file_put_begin with forPrompt writes a random token to the upload's .owner file and answers it as ownerToken. The sender's references carry it in _meta.acpmux.upload.owned, and only an upload whose .owner matches counts as the prompt's own (file_uploads::owned_dir). So a reference can't mark someone else's upload as its own, and owned: true deletes nothing. The tokens are read off the runtime. The daemon deletes owned uploads when the prompt is refused before the chat takes it: OwnedUploads covers the checks, and Saved covers the chat's own refusal or an unanswered drop. A prompt the chat takes, a duplicate, and a held prompt keep them.
    • The outbox deletes the owned uploads of a send that it answered with an earlier delivery of the same prompt id. It keeps every upload the earlier delivery names, by reference or by a link resolved to its path (split on / and \ alike, so a Windows peer's path counts too), whatever the spelling of its id, and each cancel carries the owner token.
    • A message an agent pair holds keeps its owned uploads together with its inline folder (held_dirs). They are deleted when the held message is dropped, or when its release never ran.
    • A prompt the chat took and then withdrew from its queue, or refused when its turn came, deletes its owned uploads too.
    • Deleting an upload takes the sweep's lock and then that upload's own lock (file_uploads::remove_uploads).
  • An agent a remote device drives (a Web turn, or a last turn that was one) sends no files with a message. send_message asks the daemon first (_acpmux/subsession/send with attachCheck: true), so it reads no path for a send that would be refused. subsession_send refuses any attachments from such a caller with attach.remote before it reads or resolves them, and receive/resolve get the caller's Web state. This closes a hole that is already on base: send_message read a path as the agent's own user and sent it inline, with web=false.
    • The daemon applies the same check to every call from a connection that names its caller session, which is the token acpmux send and the MCP server send from an agent's environment (server/requests.rs caller_sends_no_files). It refuses attach.remote for session/prompt and _acpmux/send_queue (the --no-wait outbox) with file or upload-ref blocks, and for the upload calls (file_put, _begin, _chunk, _finish, _cancel). send_message with an empty attachments list is a text message and skips the check. So acpmux send --attach from such an agent's shell uploads and sends nothing. The CLI's attach errors now carry their reason.
  • A peer's chat gets the upload on the peer (peer/id).
  • send_message sends the reference blocks too: _acpmux/subsession/send accepts them and resolves them before the receiver's turn (hub/subsession_send.rs), as session/prompt does.

On the receiving side the MAX_FILE_BYTES, MAX_MESSAGE_BYTES and MAX_FILES refusals are gone (check_sizes deleted). check_wire now checks only that a raw protocol request with inline files fits one JSON-RPC message on the next hop, so it gets an answer instead of being dropped by a hop. Neither sender can reach that check, since both switch to references first. Its message now says to send the files as uploads.

The send_message schema loses maxItems and the cap sentence. --attach help, the README and the skill say "any size and number".

Not in this PR: the app composer's own attachment limit (home.composer.attach.tooMany, Swift), which belongs to the composer owner.

Risk: contained to sends with attachments. Small attachments take the same inline path as before. Larger ones need the receiving daemon to resolve references (#25730, promptUploads). An older peer is refused with upload.unsupported, naming the peer and acpmux host update, where before it was refused for size.

Tests

  • send_attach_cli.rs send_attach_sends_files_of_any_size_and_number_by_reference, against a real daemon:
    • acpmux send --attach with a 24 MiB binary, a 6 MiB PNG and 20 screenshots (22 files). The screenshots reach the agent as images; the binary and the large PNG arrive as resource_link with their paths. SHA-256 is intact and the files are owner-only.
    • 5 MiB of text plus an 8 MiB file (past one frame) also goes through.
  • send_attach.rs a_peer_that_does_not_save_attachments_is_refused_before_anything_is_sent: a begin with forPrompt to a peer without promptUploads is refused upload.unsupported and reaches nothing; a plain begin still goes. It fails on the previous head 281386808ce (run 40439544930).
  • send_attach.rs a_refused_prompt_leaves_none_of_its_own_uploads_behind: covers a prompt refused by its checks, one the chat refuses (steer.unavailable), one that runs (which keeps its upload), and a cancel of a finished upload. It fails on its parent 7393c04a7ad (run 40445698527).
  • send_attach.rs: inline files past the old caps (8 MiB + 1, two halves of 10 MiB, 20 files) are saved, including through --no-wait (outbox) and to a peer's chat. A web client still cannot attach.
  • send_attach_family.rs: a 6 MiB PNG uploaded to a child and sent by reference through subsession/send reaches its agent by path, bytes intact.
  • send_attach.rs a_withdrawn_queued_prompt_leaves_none_of_its_own_uploads_behind. Units: a_taken_prompt_that_never_ran_leaves_none_of_its_own_uploads, and a_held_message_keeps_its_files_until_it_is_dropped_or_its_release_never_ran, which now carries an owned upload. All three fail on 77fdf1a (run 40452127624).
  • subsessions_safety.rs an_agent_a_web_turn_drove_cannot_send_files_with_a_message: an inline file, an upload reference and the attachCheck are all refused attach.remote, and nothing reaches the child. Before the fix the inline send was accepted and started a turn in the child (run 40459662099).
  • subsessions_safety.rs an_agent_a_web_turn_drove_sends_no_files_from_its_shell_or_its_tools runs the acpmux binary as that agent, with its caller token:
    • acpmux send --attach with a small file (inline), a 5 MiB file (by upload) and --no-wait each fail with the remote-device reason;
    • acpmux subsession-mcp send_message with a path that does not exist answers the attach.remote refusal, not file-not-found, so the path is never read;
    • the child gets only its start prompt, and no upload folder is made for it.
      On the previous head e19104ef2cb the inline acpmux send --attach succeeded (run 40465938084).
  • send_attach.rs a_refused_prompt_leaves_none_of_its_own_uploads_behind also sends owned: true and a wrong token for an existing upload: the upload stays. A cancel with no token or a wrong one leaves a finished upload, and one with its token deletes it. a_cancel_through_a_peer_deletes_only_with_the_owner_token checks the same through a peer link.
  • send_attach.rs a_repeated_queued_send_deletes_only_its_own_new_uploads: a repeat of a delivered send_queue prompt id deletes the upload it began. It keeps the delivered prompt's upload, even named in upper case, an upload it names with a forged token, and an upload the delivered prompt links to by path. With the link not counted, it fails (run 40470385633).
  • subsessions_safety.rs: send_message with attachments: [] from the Web-driven agent is sent as a text message.
  • On e5eeb6e9401 (tests only, run 40469083026) all four new checks fail: the cancel without a token deleted the upload (here and through the peer), the repeat deleted the delivered prompt's upload, and the empty list was refused.
  • Unit: small_files_go_inline_and_the_rest_by_reference (each threshold; every inline message the senders make passes check_wire).

Results (cmux-ci test, 32-vCPU Linux):

  • The tests on acpmux: a prompt names its files as uploads, of any size, by reference #25730's head (edc86435fa5, test commit only; run 40433931184): 5 failed. send_attach_cli: 22 attachments: a message takes at most 16. send_attach: 3 (past the old caps, queued, peer). send_attach_family: the by-reference block is refused as not an attachment.
  • Unit a_link_names_its_upload_on_any_platform: a POSIX path and a Windows path each name their upload. On f6a414c2f74 the Windows one is missed (run 40472624765).
  • Head a0de7dc2085: fmt, clippy, the prompt_uploads units and send_attach pass (run 40472623355). f6a414c2f74, which differs only in that function, passes the full acpmux suite except the six base failures (run 40471492169), and the attach, upload, outbox and Web-turn tests (run 40470388422).
  • 474370107ab (run 40469087813, full acpmux suite, fmt and clippy): fmt and clippy are clean, and every attach, upload, prompt-upload and Web-turn test passes. The only failures are the six that fail the same way on base 9c6435d (run 40453283684): own_servers_run_under_the_daemon_prefix, hermetic_daemon_guard ×2, daemon_chat_open, ephemeral_run, pooled_sessions_end_with_the_daemon_and_after_a_crash.

Size: 654+/144− in source and 485+/55− in tests. That is over the usual 800 lines, because the review rounds added the owned-upload lifecycle, its owner token and the Web-control fix. The fix changes the same subsession_send lines, so it could not land on its own.

Agent session: agent session

🤖 Generated with Claude Code

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 27, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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

@blacksmith-sh

This comment has been minimized.

teamleaderleo added a commit that referenced this pull request Oct 27, 2026
…ned references (#25840)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
@teamleaderleo
teamleaderleo force-pushed the hb-busy-accept/attach-by-reference branch from b2a3d10 to 77fdf1a Compare October 27, 2026 03:42
@github-actions github-actions Bot added risk:high Risk tier (docs/ci/risk-tiers.md) and removed risk:normal Risk tier (docs/ci/risk-tiers.md) labels Oct 27, 2026
@teamleaderleo
teamleaderleo changed the base branch from feat-cmux-next to hb-busy-accept/upload-resume October 27, 2026 03:47
@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 a0de7dc: 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=pr25840 -f ref=hb-busy-accept/attach-by-reference -f platform=macos -f backend_mode=local

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next guards / nightly-guards / workflow-guard-tests / release-notary is red on 12 of 18 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

@blacksmith-sh

blacksmith-sh Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Found 8 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmpj6qzjgv_/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmplor8jaqj/summary-fail.json (live):/summary-fail.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
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)/
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)
View Logs

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

… peer's linked upload is kept too

Test: prompt_uploads a_link_names_its_upload_on_any_platform (fails on f6a414c).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
@teamleaderleo
teamleaderleo marked this pull request as ready for review October 27, 2026 08:38
@teamleaderleo
teamleaderleo merged commit 30d0e84 into feat-cmux-next Oct 27, 2026
129 of 135 checks passed
@teamleaderleo
teamleaderleo deleted the hb-busy-accept/attach-by-reference branch October 27, 2026 08:38
teamleaderleo added a commit that referenced this pull request Oct 27, 2026
#25840 made a forPrompt upload removable only by a cancel carrying its
ownerToken, and made upload.owned carry that token instead of true. The
contract doc still described the earlier shape. It now says what begin
returns, what cancel removes, what owned's value is, and the app's path:
begin forPrompt, keep the token natively, leave owned out of prompt refs.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

austinywang added a commit that referenced this pull request Oct 27, 2026
…ase red)

Same change as #26087, combined here because each is red on the other's red:
check-state-roots counts the inline test module's /home/me/.acpmux/... fixture
(since #25840). upload_ids reads only the parent folder, so the test is the same.

Test: bash scripts/cmux-next/check-state-roots.sh .: exit 1 on the base, ok here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 27, 2026
check-state-roots (baseline 0) flags the fixture path /home/me/.acpmux/uploads/... added by #25840. The
test checks that a link names its upload by the parent folder on any platform; the root's name plays no
part (upload_ids reads the second-to-last path segment), so a neutral root keeps the test's intent.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-6ccd4e4ea4a1d1f1ddd4

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
cmux-bot Bot pushed a commit that referenced this pull request Oct 30, 2026
* docs(next): the Cloud start-session highlight links its docs (release-notary red on base)

test_the_repository_highlights_validate fails on every feat-cmux-next PR: the
highlight from #25625 has no tryIt action or docs link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(nightly): read the legacy publish job with its moved step scripts inline

#25879 moved "Generate the old feed's
appcast" into scripts/ci/nightly-steps/legacy-identity-appcast.sh, so
test_the_appcast_comes_from_the_tested_plist no longer found its commands in
nightly.yml (ValueError: substring not found) and release-notary went red on
feat-cmux-next. Read the workflow through nightly_workflow_text.nightly_text(),
as the other nightly workflow tests do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(nightly): leave test-execution.toml as on the base

The pre-commit hook registered two base tests that this PR does not touch;
keep this PR to the two red fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* release notes: two new fixed highlights get their Try it action (base red)

home-composer-border (#26119) and selected-harness-start (#26106) landed with
neither an action nor a docs link, which scripts/whats-new/validate.py requires,
so release-notary's test_the_repository_highlights_validate is red on
feat-cmux-next. The Chief field one opens Home; the harness one starts a new
agent chat.

Test: python3 scripts/whats-new/test_digest.py: 1 failure before, OK after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* nightly: the Sparkle key test reads nightly.yml with its moved steps inline (base red)

#26196 and #26210 moved two of the four SPARKLE_PRIVATE_KEY="$(./scripts/ci/nightly-sparkle-key.sh)"
call sites into scripts/ci/nightly-steps/, so the raw workflow has two and
tests/test_nightly_sparkle_key.sh (release-notary) fails its at-least-three check.
It now greps nightly_workflow_text.nightly_text(), the expansion the other
workflow tests read.

Test: bash tests/test_nightly_sparkle_key.sh: FAIL on the base, PASS here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next routing: a test's home-folder Claude settings path is not a repository read (base red)

CloudCredentialTransferTests named ".claude/settings.json" under a temp home
in one literal; ci-target-graph.py found the repository's own file at that
path and listed it as a CmuxNextAppTests read (base autofix c14a002), which
cmux-next.yml's pull_request paths do not cover, so Tier routing's
test_every_routed_input_triggers_the_workflow is red on feat-cmux-next. The
test builds the path from folder and file, and the graph loses the read
(literal_reads recomputed: the target's other 14 reads are unchanged).

Test: python3 tests/test_cmux_next_route.py: 1 failure on the base, OK here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux: the upload-link test names no literal ~/.acpmux state root (base red)

Same change as #26087, combined here because each is red on the other's red:
check-state-roots counts the inline test module's /home/me/.acpmux/... fixture
(since #25840). upload_ids reads only the parent folder, so the test is the same.

Test: bash scripts/cmux-next/check-state-roots.sh .: exit 1 on the base, ok here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* acpmux: collapse the Codex broker's account checks into let chains (clippy base red)

#26191 nested two if-lets in codex_auth_broker.rs; clippy -D warnings
(collapsible_if) fails on it for every PR that touches acpmux. Same checks,
one let chain each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* release notes: the compact thought-row highlight gets its Try it action (base red)

#26270 added it with neither an action nor a docs link, so release-notary's
test_the_repository_highlights_validate is red again.

Test: python3 scripts/whats-new/test_digest.py: 1 failure before, OK after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* tests: leave test-execution.toml as the base has it

The pre-commit hook registered unrelated tests while committing the base merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* cmux-next tests: drop the settings-path split; the graph skips .claude paths itself

#26248 made ci-target-graph.py ignore .git, .claude and .codex paths a test
names, so the one-literal form no longer routes CmuxNextAppTests on the
repository's settings file. Back to the base's line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(next): the Cloud start-session highlight links its docs (release-notary red on base)

test_the_repository_highlights_validate fails on every feat-cmux-next PR: the
highlight from #25625 has no tryIt action or docs link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(next): give the two base highlights release-notary rejects a live action

feat-cmux-next's inline-pr-pills and inspector-error-colours highlights have
no action or docs line, so scripts/whats-new/test_digest.py
(release-notary) fails on the base. Use existing palette actions rather than
https://cmux.com/docs/agents, which returns 404.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(next): give the new Find in Chat highlight a live action (release-notary red on base since 93ff53c)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Leo <cheerleaderleo@outlook.com>

This branch was previously deployed

3 inactive deployments
artifacts — a0de7dc2 Deployed Oct 27, 2026 by teamleaderleo via add the Windows daemon to the tree #63229
sentry-dsyms — a0de7dc2 Deployed Oct 27, 2026 by teamleaderleo via upload cmux-tui dSYMs to Sentry #63229
ci-routing-agent — a0de7dc2 Deployed Oct 27, 2026 by teamleaderleo via wait for same-key tree publisher #63229
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