Repository navigation
carry of #27073: NEXT: Cloud and peer chat uploads take files of any size - #28695
Conversation
Leo, 2026-10-26: "WHY THE FUCK SHOULD THERE BE A FUCKING SIZE CAP ANYWAYS ... I ALREADY SAID WE NEED ARBITARRILY LARGE FILES OF ANY TYPE". These fail on base, which caps Cloud uploads at 32 MiB a file and 64 MiB a batch and peer uploads at 1 GiB: - CloudUploadsTests: a staged Cloud begin past 32 MiB, and a batch past the 64 MiB total, are taken; no language of cloud.upload.failed or cloud.upload.refused carries a placeholder or a number. - AgentPeerUploadsTests: an inline peer upload past 1 GiB is sent; a staged peer begin past 1 GiB, alone or as a batch's first file, and a batch past the 1 GiB total, are taken. - AgentPaneFilePutTests: a chunked put past 1 GiB sends its begin. Sizes are only declared (Data(count:) or a declared size); no bytes are written. To test the Cloud begin without a live Cloud machine, its admission check moves, unchanged, into CloudUploads.beginRefusal(name:size:manifest:). The old test that the refusal names the limits is dropped. Split out of #27016 (Cloud and peer stager caps); the peer test drop and the "refusal names no limit" idea come from #27016. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-3f783e95b72165fe52b9
Leo, 2026-10-26: "WHY THE FUCK SHOULD THERE BE A FUCKING SIZE CAP ANYWAYS
... I ALREADY SAID WE NEED ARBITARRILY LARGE FILES OF ANY TYPE".
Cloud (CloudUploads): drop maximumFileBytes (32 MiB) and
maximumTotalBytes (64 MiB), the guard in upload(_:to:services:), and in
a staged begin the per-file size check, the manifest's per-file and
total checks and the per-session staged total. A begin still needs a
non-negative size and a manifest that starts with the file begun.
Peer (acpmux file_put): drop AgentPaneUpload.maximumFileBytes,
maximumTotalBytes and maximumPeerFileBytes (1 GiB), the four guards in
AgentPaneFilePut, the begin and manifest checks in AgentPeerUploads'
stager and the guard in AgentPeerUploads.upload, and the base64 and
total limits in AgentPaneUpload.files. A peer that refuses a file
(filePut.tooLarge) still says so.
The chunk bound in both stagers is written size - offset, so a huge
declared size cannot overflow it.
Localization: cloud.upload.refused named the limits ("up to %1$@ each,
%2$@ in all") in every locale. There are no limits to name, so the key
and CloudUploadStrings.refused are gone; a refusal from the machine
shows cloud.upload.failed ("Couldn't send the file to the Cloud
machine."), already translated in every locale, and keeps its
upload_refused code for the page.
Split out of #27016 (Cloud and peer stager caps).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-3f783e95b72165fe52b9
…eer (red) Review of #27073: the request decoder (AgentPaneRequest+Decoding) still refuses a staged begin, or a manifest entry, over 1 GiB, and a manifest of more than 64 files, before CloudUploads or AgentPeerUploads see it. The stager tests called the stagers directly and missed it. These fail on this head: - AgentPeerUploadsTests.aDecodedBeginOfAnySizeReachesThePeerStager and CloudUploadsTests.aDecodedBeginOfAnySizeReachesTheCloudStager (2 GiB and 8 GiB): a chat.upload.begin decoded through AgentPaneRequest(body:), the bridge's own decoding, alone, as a batch's first file, and as a small first file whose manifest names a file over 1 GiB, must decode into the staged begin and be taken by the peer stager and the Cloud stager's begin check (DecodedStagedBegins builds the requests). - AgentPaneUploadTests.stagedUploadAcceptsAnyNonnegativeFileSize and stagedUploadAccepts65LargeFiles, with stagedUploadRejectsNegativeFileAndBatchSizes, taken verbatim from #26409 so the two PRs' test hunks are identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-3f783e95b72165fe52b9
Leo, 2026-10-26: "WHY THE FUCK SHOULD THERE BE A FUCKING SIZE CAP ANYWAYS ... I ALREADY SAID WE NEED ARBITARRILY LARGE FILES OF ANY TYPE". AgentPaneRequest+Decoding: drop maximumUploadStageFile (1 GiB) from a chat.upload.begin's size and each manifest entry's size, and the 64-entry manifest limit. A begin still needs a file name and a non-negative size; a manifest still needs one or more well-formed entries. A chunk stays at most 512 KiB. This is #26409's change to the same file, line for line (its 3813876 and 951315e), so whichever of #26409 and #27073 lands second merges it cleanly. Cloud and peer begins do not pass through AgentChatUploads (CloudHandlers+Move tries Cloud, then peer, then the local stager), so the local stager's caps stay #26409's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-3f783e95b72165fe52b9
`cmux link access upload` (the Cloud chat's finish step) still refuses a file over 1 GiB, so a file the stager took whole fails at the end with upload_refused. The test pastes a sparse 1 GiB + 1 byte file and uploads it to a fake fs-v1 machine: it must be accepted and arrive whole, in parts of at most 8 MiB (overwrite, then appends). Leo: "Attachments take arbitrarily large files of any type; there is no size cap". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-3f783e95b72165fe52b9
`cmux link access upload` refused any file over 1 GiB (MAX_FILE_BYTES), so a Cloud chat upload the stager took whole still failed at finish with upload_refused. The cap is gone. The file was already streamed: copy_file hands an open file to Remote::write, which reads and sends it in 8 MiB fs.write parts (overwrite, then appends to a temporary name, then a rename), so nothing reads the whole file into memory. The uploads.rs change is taken byte-identical from #27016 (launch-polish), so the two merge cleanly; credit to that PR. Leo: "Attachments take arbitrarily large files of any type; there is no size cap". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-3f783e95b72165fe52b9
…ncap-cloud-peer-stager # Conflicts: # Packages/macOS/CmuxNext/Tests/CmuxNextAgentPaneTests/AgentPaneUploadTests.swift
…27073-attach-uncap
…clares (red) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-68ba07270dde07b96ff4
Cloud and peer uploads no longer declare their file and batch caps, nor the CLI its MAX_FILE_BYTES, so their exemptions go; AgentPaneDraftAttachments' maximumBytes was already gone on base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-68ba07270dde07b96ff4
…every field Base's #24473 read AgentPaneUpload.maximumFileBytes and maximumTotalBytes for the macOS capture-thumbnail recording snapshot, which this branch removes, so the merged tree did not compile. The snapshot reads the recording into memory before saving it, so it keeps the same 1 GiB as its own named budget (AgentPaneRecordingSnapshot.maximumPinnedBytes / maximumPinnedTotalBytes). The Cloud and peer Stage initializers name expiry and cleanup explicitly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-68ba07270dde07b96ff4
The two App stagers each kept their own stages, batches, expiry deadlines and cleanup tasks (+131 CmuxNextApp lines, which the arch ratchet refuses). AgentPaneStagedUploads<Target> now owns that once: a stage of any size, a batch authorized while its stages live, one DemandTimer for abandoned stages and batches (the peer stager no longer sleeps a task per stage), a transfer that owns its file until it returns, and a cancel that stops the transfer before the staged file is removed. Each App stager keeps only its own target checks, file layout and transfer. AgentPaneStagedUploadsTests covers begin of any size, batch order, chunk ownership, and cancel during the transfer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-68ba07270dde07b96ff4
…27073-attach-uncap
…ered file back Review of the carry (Astra, BLOCK at 6ff0fa8): - The older one-frame upload sent every file in one CLI call, under no upload id: more than 64 files were refused, and a failed or cancelled call left its partial file and the files already sent on the machine. CloudAccessCLI.upload now sends each file in its own call under an id of the app's, and a failure or a cancel removes every file of the upload (removeUploads) before it answers. - A cancel that came after the transfer delivered the file (the chip's x while the answer was on its way) found no stage and left the file on the machine. AgentPaneStagedUploads keeps a finished stage's undo for its lifetime; the Cloud stager's undo removes that upload id. Peers have no remote delete yet. - CloudBringBackTests expects the upload id on the CLI call (the original already sent it for one file). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-68ba07270dde07b96ff4
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Gallery before/after for
States
Pick other states with a Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by web and size checks: green Default changesThis pull request changes 9 defaults at
Needs a review. It passes with an independent review receipt on this commit: a comment or review whose first line is Merge receipt: merged as Regenerate the snapshot with Shipped in:
|
…es before and after Review of the carry (Astra, BLOCK at f6edd0e): removeUploads stopped at the first failed removal, leaving later files on the machine; it now tries every id and throws after. The late-cancel test expected no undo at all on its second file, though the first had rightly been undone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-68ba07270dde07b96ff4
|
Review (codex gpt-6-astra high fast, direct codex exec): SHIP at 2058cd2 Round 5, a delta from 9d4b5d8 (SHIP). The fleet test compile refused |
|
Found 82 test failures on Blacksmith runners: Failures
...and 72 more test failures. View all on Blacksmith
|
|
Orphaned: no activity for 12 h, and no lane holds a lease on it. The orphan sweep could not merge |
|
Owned by an agent session · took over from an earlier session · session |
Base's #29626 dropped the floating-preview pin cap (APFS clone, any size), so its AgentPaneDroppedPaths, AgentPaneRecordingSnapshot and their drop test win over this branch's renamed 1 GiB pin budget. The webview upload signatures keep both sides: this branch's subscribable UploadCancelled and base's per-file progress reporter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
|
Review (codex-review-box/gpt-6-astra high, lane-place): BLOCK at b3a0750 BLOCK at b3a0750 Visual verdict: no images; Leo tries the build
The standalone attachment-cap guard passes. Swift, Rust, and Bun suites could not run because their toolchains are unavailable in this box. |
Base removed MAX_FILE_BYTES from access_cli/uploads.rs, so the guard's exemption named a cap that no longer exists and tests/test_attachment_caps_guard.py failed (1 of 19). python3 -I tests/test_attachment_caps_guard.py: 19 OK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
|
Review (codex-review-box/gpt-6-astra high, lane-place): BLOCK at 2d4e117 BLOCK at 2d4e117 Visual verdict: no images; Leo tries the build
Validation: 19 guard tests and four Node/Babel smoke checks passed. Bun, Swift, and Rust suites were unavailable in this box. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
There was a problem hiding this comment.
16 issues found across 32 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Packages/macOS/CmuxNext/Tests/CmuxNextDaemonTests/ProcessRunnerProgressDeadlineTests.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Tests/CmuxNextDaemonTests/ProcessRunnerProgressDeadlineTests.swift:79">
P2: This test passes even if the up-front deadline guard is removed, because a missing executable also throws a `DaemonError`. Assert the exact case so the test verifies the refusal happens before launch.</violation>
</file>
<file name="tests/test_attachment_caps_guard.py">
<violation number="1" location="tests/test_attachment_caps_guard.py:51">
P2: This check only tests that each exempted name appears somewhere in the file, not that it is declared. `maximumSize` in AttachmentMedia.swift is satisfied by the AVFoundation property assignment `generator.maximumSize = ...`, so the exemption survives after the cap is removed. Match a declaration form (for example, the preceding token is `let`, `var`, `const`, `static`, or `fn`) so the test enforces what its name says.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneStagedUploads.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneStagedUploads.swift:114">
P2: `chunk` performs its file write synchronously on the main actor. Route the append through `work.run(.userBlocking, mayBlock: .files, ...)` like the unlink in `remove`, so the main thread does not block on disk I/O.</violation>
<violation number="2" location="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneStagedUploads.swift:115">
P2: A failed or partial write leaves bytes beyond `received`, and the retry appends after them via `seekToEnd()`, which corrupts the staged file. Truncate to `offset` and write at that position, and close the handle on every path.</violation>
<violation number="3" location="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneStagedUploads.swift:159">
P2: Late-delivery cleanup failures are reported as successful cancellations and cannot be retried because the delivery is removed before this undo completes. Make undo report failure and retain or otherwise surface the delivery when remote deletion fails.</violation>
<violation number="4" location="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneStagedUploads.swift:166">
P2: The purge loop removes stages from a snapshot taken before its awaits, so a stage that begins transfer in the meantime still gets its file unlinked. Re-check idleness at each iteration before calling `remove`.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudAccessCLI.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudAccessCLI.swift:98">
P2: If cleanup fails during a refusal, `removeUploads` throws `CancellationError` from a task that was never cancelled. That replaces the CLI's `errorCode` with a generic failure and logs a misleading error. Keep the refusal answer and ignore the cleanup result here.</violation>
</file>
<file name="webviews/src/agent-session/acpmux/cloudUploads.ts">
<violation number="1" location="webviews/src/agent-session/acpmux/cloudUploads.ts:9">
P2: Retrying a staged image's original still passes only `gone`, so it never subscribes to this cancellation signal. Removing that chip during a long native finish cannot abort the native call and delays `chat.upload.cancel` until finish returns; pass a subscribed `UploadCancelled` callback in `retryOriginal`.</violation>
<violation number="2" location="webviews/src/agent-session/acpmux/cloudUploads.ts:179">
P2: `chat.upload.cancel` now goes through the 30s step deadline and is awaited. If the host is slow to answer, each cancelled or failed file blocks the batch loop for up to 30s before the next file starts. The earlier code deliberately sent cancel straight to the host, so this reverses that choice; either keep it unbounded and documented, or confirm the wait is intended.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudUploads.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudUploads.swift:161">
P1: Cancellation reports success even when remote cleanup fails. This `try?` swallows `removeUploads`'s error, so a remote file can remain after the page receives a successful cancel; propagate or retry the cleanup failure instead.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneUpload.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneUpload.swift:23">
P3: This adds a second `10 * 60` lifetime for the same batch deadline that `AgentPaneStagedUploads.lifetime` already defines. Reuse one constant so the stage and batch expiries cannot diverge.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneFilePut.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneFilePut.swift:9">
P2: Removing the size guard routes files over 10 MiB to `filePutChunks`. A peer that only advertises `filePut` now gets `.unsupported` instead of `.tooLarge`, so the composer says the chat takes no uploads. Map `unsupportedFeature(filePutChunksFeature)` to `.tooLarge` on the chunked path, or keep the one-shot size cap for peers without that feature.</violation>
</file>
<file name="cmux-tui/crates/cmux-tui/src/link/access_cli.rs">
<violation number="1" location="cmux-tui/crates/cmux-tui/src/link/access_cli.rs:326">
P3: This `upload-cancel` match arm is 118 columns, over the crate's rustfmt `max_width = 100`, so `cargo fmt --check` will flag it. Split the pattern the way the `answer` arm is split.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextApp/AgentPeerUploads.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextApp/AgentPeerUploads.swift:62">
P2: A peer upload that finishes before the page’s cancellation arrives cannot be undone: this call supplies no `undo` closure, so the shared store forgets the delivered file and leaves it on the peer. Add a peer-side remote deletion/undo path and pass it to `finish`.</violation>
</file>
<file name="cmux-tui/crates/cmux-tui/src/link/sync_cli/remote.rs">
<violation number="1" location="cmux-tui/crates/cmux-tui/src/link/sync_cli/remote.rs:234">
P1: The upload heartbeat is emitted only after `self.call` receives the `fs.write` response, so it cannot keep the 30-second idle deadline alive while a chunk is still in flight. Emit progress before or periodically during each potentially blocking write; the added in-flight progress test currently observes no tick and fails.</violation>
</file>
<file name="Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Launch/ProcessRunner.swift">
<violation number="1" location="Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Launch/ProcessRunner.swift:133">
P2: `waitForDeadline` can busy-spin indefinitely when an idle-only caller supplies a zero or negative `progressIdle`: the sleep returns immediately and `waited` never reaches a deadline while progress keeps changing. Treat non-positive idle durations as an immediate timeout or reject them before entering the loop.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| // A cancel after the file arrived (the chip's x as the answer comes back) removes it from the machine. | ||
| guard done.answer.ok, !done.answer.uploadIDs.isEmpty, let socket = done.socket else { return nil } | ||
| return { | ||
| try? await CloudAccessCLI(binary: done.binary).removeUploads(socket: socket, remoteRoot: done.remoteRoot, ids: done.answer.uploadIDs) |
There was a problem hiding this comment.
P1: Cancellation reports success even when remote cleanup fails. This try? swallows removeUploads's error, so a remote file can remain after the page receives a successful cancel; propagate or retry the cleanup failure instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextApp/Cloud/CloudUploads.swift, line 161:
<comment>Cancellation reports success even when remote cleanup fails. This `try?` swallows `removeUploads`'s error, so a remote file can remain after the page receives a successful cancel; propagate or retry the cleanup failure instead.</comment>
<file context>
@@ -120,95 +104,77 @@ final class CloudUploads {
+ // A cancel after the file arrived (the chip's x as the answer comes back) removes it from the machine.
+ guard done.answer.ok, !done.answer.uploadIDs.isEmpty, let socket = done.socket else { return nil }
+ return {
+ try? await CloudAccessCLI(binary: done.binary).removeUploads(socket: socket, remoteRoot: done.remoteRoot, ids: done.answer.uploadIDs)
+ }
+ })
</file context>
| "fs.write", | ||
| json!({ "path": path, "bytes_base64": encode(&first), "mode": "overwrite", "permissions": permissions }), | ||
| )?; | ||
| progress(); |
There was a problem hiding this comment.
P1: The upload heartbeat is emitted only after self.call receives the fs.write response, so it cannot keep the 30-second idle deadline alive while a chunk is still in flight. Emit progress before or periodically during each potentially blocking write; the added in-flight progress test currently observes no tick and fails.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui/src/link/sync_cli/remote.rs, line 234:
<comment>The upload heartbeat is emitted only after `self.call` receives the `fs.write` response, so it cannot keep the 30-second idle deadline alive while a chunk is still in flight. Emit progress before or periodically during each potentially blocking write; the added in-flight progress test currently observes no tick and fails.</comment>
<file context>
@@ -217,6 +231,7 @@ impl Remote {
"fs.write",
json!({ "path": path, "bytes_base64": encode(&first), "mode": "overwrite", "permissions": permissions }),
)?;
+ progress();
return revision_of(&entry);
}
</file context>
| } | ||
|
|
||
| @Test func missingBothDeadlinesRefusesBeforeLaunchingAChild() async { | ||
| await #expect(throws: DaemonError.self) { |
There was a problem hiding this comment.
P2: This test passes even if the up-front deadline guard is removed, because a missing executable also throws a DaemonError. Assert the exact case so the test verifies the refusal happens before launch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Tests/CmuxNextDaemonTests/ProcessRunnerProgressDeadlineTests.swift, line 79:
<comment>This test passes even if the up-front deadline guard is removed, because a missing executable also throws a `DaemonError`. Assert the exact case so the test verifies the refusal happens before launch.</comment>
<file context>
@@ -0,0 +1,84 @@
+ }
+
+ @Test func missingBothDeadlinesRefusesBeforeLaunchingAChild() async {
+ await #expect(throws: DaemonError.self) {
+ _ = try await ProcessRunner.run(executable: URL(fileURLWithPath: "/does-not-exist"), arguments: [],
+ environment: nil, timeout: nil, progressIdle: nil, clock: ContinuousClock())
</file context>
| await #expect(throws: DaemonError.self) { | |
| await #expect(throws: DaemonError.launchFailed("process requires an elapsed or idle deadline")) { |
| self.assertTrue(path.is_file(), relative) | ||
| if not path.is_file(): | ||
| continue | ||
| names = {token[0] for token in guard.code_tokens(path.read_text(encoding="utf-8"))} |
There was a problem hiding this comment.
P2: This check only tests that each exempted name appears somewhere in the file, not that it is declared. maximumSize in AttachmentMedia.swift is satisfied by the AVFoundation property assignment generator.maximumSize = ..., so the exemption survives after the cap is removed. Match a declaration form (for example, the preceding token is let, var, const, static, or fn) so the test enforces what its name says.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/test_attachment_caps_guard.py, line 51:
<comment>This check only tests that each exempted name appears somewhere in the file, not that it is declared. `maximumSize` in AttachmentMedia.swift is satisfied by the AVFoundation property assignment `generator.maximumSize = ...`, so the exemption survives after the cap is removed. Match a declaration form (for example, the preceding token is `let`, `var`, `const`, `static`, or `fn`) so the test enforces what its name says.</comment>
<file context>
@@ -39,6 +39,20 @@ def test_retained_bounds_only_apply_to_the_named_path_and_symbol(self):
+ self.assertTrue(path.is_file(), relative)
+ if not path.is_file():
+ continue
+ names = {token[0] for token in guard.code_tokens(path.read_text(encoding="utf-8"))}
+ for name in symbols:
+ with self.subTest(path=relative, name=name):
</file context>
| guard var stage = stages[token], stage.idle, owns(stage.staged.target), stage.received == offset, | ||
| data.count <= Self.chunkBytes, data.count <= stage.size - offset else { return nil } | ||
| do { | ||
| let handle = try FileHandle(forWritingTo: stage.staged.file) |
There was a problem hiding this comment.
P2: chunk performs its file write synchronously on the main actor. Route the append through work.run(.userBlocking, mayBlock: .files, ...) like the unlink in remove, so the main thread does not block on disk I/O.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneStagedUploads.swift, line 114:
<comment>`chunk` performs its file write synchronously on the main actor. Route the append through `work.run(.userBlocking, mayBlock: .files, ...)` like the unlink in `remove`, so the main thread does not block on disk I/O.</comment>
<file context>
@@ -0,0 +1,208 @@
+ guard var stage = stages[token], stage.idle, owns(stage.staged.target), stage.received == offset,
+ data.count <= Self.chunkBytes, data.count <= stage.size - offset else { return nil }
+ do {
+ let handle = try FileHandle(forWritingTo: stage.staged.file)
+ try handle.seekToEnd(); try handle.write(contentsOf: data); try handle.close()
+ } catch { return nil }
</file context>
| if let stage = stages[token], stage.target.session == target.session { | ||
| stages.removeValue(forKey: token) | ||
| try? FileManager.default.removeItem(at: stage.file) | ||
| let result = await stages.finish(token, owns: owns, work: services.work) { staged -> Result<String, AgentPaneFilePutFailure> in |
There was a problem hiding this comment.
P2: A peer upload that finishes before the page’s cancellation arrives cannot be undone: this call supplies no undo closure, so the shared store forgets the delivered file and leaves it on the peer. Add a peer-side remote deletion/undo path and pass it to finish.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextApp/AgentPeerUploads.swift, line 62:
<comment>A peer upload that finishes before the page’s cancellation arrives cannot be undone: this call supplies no `undo` closure, so the shared store forgets the delivered file and leaves it on the peer. Add a peer-side remote deletion/undo path and pass it to `finish`.</comment>
<file context>
@@ -35,67 +32,45 @@ struct AgentPeerUploads {
- if let stage = stages[token], stage.target.session == target.session {
- stages.removeValue(forKey: token)
- try? FileManager.default.removeItem(at: stage.file)
+ let result = await stages.finish(token, owns: owns, work: services.work) { staged -> Result<String, AgentPaneFilePutFailure> in
+ do {
+ return .success(try await staged.target.host.putFile(sessionId: staged.target.session, name: staged.name, file: staged.file))
</file context>
| } | ||
| var seen = progress() | ||
| var waited = Duration.zero | ||
| while timeout.map({ waited < $0 }) ?? true { |
There was a problem hiding this comment.
P2: waitForDeadline can busy-spin indefinitely when an idle-only caller supplies a zero or negative progressIdle: the sleep returns immediately and waited never reaches a deadline while progress keeps changing. Treat non-positive idle durations as an immediate timeout or reject them before entering the loop.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextDaemon/Launch/ProcessRunner.swift, line 133:
<comment>`waitForDeadline` can busy-spin indefinitely when an idle-only caller supplies a zero or negative `progressIdle`: the sleep returns immediately and `waited` never reaches a deadline while progress keeps changing. Treat non-positive idle durations as an immediate timeout or reject them before entering the loop.</comment>
<file context>
@@ -123,8 +115,32 @@ public struct ProcessRunner {
+ }
+ var seen = progress()
+ var waited = Duration.zero
+ while timeout.map({ waited < $0 }) ?? true {
+ let window = timeout.map { min(progressIdle, $0 - waited) } ?? progressIdle
+ // wakeup-allow: one idle window, stopped on child exit/cancel; progress permits the next window.
</file context>
| while timeout.map({ waited < $0 }) ?? true { | |
| guard progressIdle > .zero else { return } | |
| while timeout.map({ waited < $0 }) ?? true { |
|
|
||
| /// Cancellation checks between chunks and notifications while the native finish is in flight. | ||
| export type UploadCancelled = ((index: number) => boolean) & { | ||
| subscribe?(index: number, changed: () => void): () => void; |
There was a problem hiding this comment.
P2: Retrying a staged image's original still passes only gone, so it never subscribes to this cancellation signal. Removing that chip during a long native finish cannot abort the native call and delays chat.upload.cancel until finish returns; pass a subscribed UploadCancelled callback in retryOriginal.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/cloudUploads.ts, line 9:
<comment>Retrying a staged image's original still passes only `gone`, so it never subscribes to this cancellation signal. Removing that chip during a long native finish cannot abort the native call and delays `chat.upload.cancel` until finish returns; pass a subscribed `UploadCancelled` callback in `retryOriginal`.</comment>
<file context>
@@ -4,6 +4,11 @@
+/// Cancellation checks between chunks and notifications while the native finish is in flight.
+export type UploadCancelled = ((index: number) => boolean) & {
+ subscribe?(index: number, changed: () => void): () => void;
+};
+
</file context>
| private var remaining: [AgentPaneUploadBatchEntry] | ||
| private var owners: Set<String> = [] | ||
| private var expires: Date | ||
| package static let lifetime: TimeInterval = 10 * 60 |
There was a problem hiding this comment.
P3: This adds a second 10 * 60 lifetime for the same batch deadline that AgentPaneStagedUploads.lifetime already defines. Reuse one constant so the stage and batch expiries cannot diverge.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At Packages/macOS/CmuxNext/Sources/CmuxNextAgentPane/AgentPaneUpload.swift, line 23:
<comment>This adds a second `10 * 60` lifetime for the same batch deadline that `AgentPaneStagedUploads.lifetime` already defines. Reuse one constant so the stage and batch expiries cannot diverge.</comment>
<file context>
@@ -14,6 +14,40 @@ public nonisolated struct AgentPaneUploadBatchEntry: Sendable, Equatable {
+ private var remaining: [AgentPaneUploadBatchEntry]
+ private var owners: Set<String> = []
+ private var expires: Date
+ package static let lifetime: TimeInterval = 10 * 60
+
+ package init(_ manifest: [AgentPaneUploadBatchEntry], now: Date = Date()) {
</file context>
| uploads::upload(socket, &root, positional) | ||
| uploads::upload_with_id(socket, &root, positional, args.upload_id.as_deref()) | ||
| } | ||
| ("upload-cancel", Args { socket: Some(socket), remote_root: Some(root), upload_id: Some(id), positional, .. }) |
There was a problem hiding this comment.
P3: This upload-cancel match arm is 118 columns, over the crate's rustfmt max_width = 100, so cargo fmt --check will flag it. Split the pattern the way the answer arm is split.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/cmux-tui/src/link/access_cli.rs, line 326:
<comment>This `upload-cancel` match arm is 118 columns, over the crate's rustfmt `max_width = 100`, so `cargo fmt --check` will flag it. Split the pattern the way the `answer` arm is split.</comment>
<file context>
@@ -314,7 +321,16 @@ pub(super) fn run(args: &[String]) -> i32 {
- uploads::upload(socket, &root, positional)
+ uploads::upload_with_id(socket, &root, positional, args.upload_id.as_deref())
+ }
+ ("upload-cancel", Args { socket: Some(socket), remote_root: Some(root), upload_id: Some(id), positional, .. })
+ if positional.is_empty() =>
+ {
</file context>
… the socket deadline Codex BLOCK on #28695: the 30 s upload idle only heard a heartbeat after a whole 8 MiB fs.write returned, so a healthy slow link was killed. The Rust Remote now writes a request line in 256 KiB slices and reports progress after each slice and on each answer; the app's upload idle is 90 s, longer than the CLI's 60 s per-read/write socket deadline (IO_DEADLINE). No size cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
|
Review (codex-review-box/gpt-6-astra high, lane-place): SHIP at 5a7c47e SHIP at 5a7c47e Visual verdict: no images; Leo tries the build
Follow-ups:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Carry of #27073 (its owner's host can't be reached; idle since 2026-10-30T04:06Z). It keeps the original commits and their authorship, merges current
feat-cmux-next, and fixes what blocked the last two reviews.Leo's words this serves: "arbitarrily large file sizes" (2026-10-25) and "why the F*** SHOULD THERE BE A F***ING SIZE CAP ANYWAYS ... I ALREADY SAID WE NEED ARBITARRILY LARGE FILES OF ANY TYPE" (2026-10-26).
What the original does (still missing on base)
Base already streams Home bucket multipart uploads of any size (#26673), phone uploads (#26954) and native staged uploads into local chats (#26409). Uploads into Cloud and ssh-peer chats still had caps until this PR:
CloudUploads.swift): the 32 MiB per-file and 64 MiB per-batch caps are gone, along with the "won't send that file" refusal text (cloud.upload.refusedremoved from Cloud.xcstrings).cmux link access upload(access_cli/uploads.rs) drops its 1 GiBMAX_FILE_BYTESand prints a progress heartbeat after each remote write.CloudAccessCLIruns the upload under a 30 s idle deadline (ProcessRunnerprogressIdle, withtimeoutnow optional) and keeps only a bounded tail of its output. Each staged transfer gets an app-chosen--upload-id, and a newupload-cancelremoves exactly that remote directory when the CLI fails or is cancelled.AgentPeerUploads.swift,AgentPaneFilePut.swift):maximumPeerFileBytesand the 1 GiB guards are gone. Only the peer can refuse a file as too large. The peer-side cancel runs from a fresh task, so a cancelled put still reaches the peer.AgentPaneUpload.swift):chat.uploaddropsmaximumFileBytes/maximumTotalBytes. A batch (AgentPaneUploadBatch) stays authorized while any of its stages lives.cloudUploads.ts,attachmentIntake.tsx,intakeFiles.ts): the chip's × aborts the finish while it is in flight. Finish has no page deadline because native owns the idle deadline. The page awaits the cancel until the host has removed its stage.What this carry fixes
Compile (finding 1,
AgentPeerUploads.swift:76) and compile (finding 2,CloudUploads.swift:159): theStageinitializers left outexpiry/cleanup. Both stagers now build their stages in one shared store (item 4), so those initializers no longer exist. Commit 77a24bb first named every field.Compile after the base merge: base's Keep recordings dropped from the macOS capture thumbnail #24473 (recording drops from the macOS capture thumbnail) read
AgentPaneUpload.maximumFileBytes/maximumTotalBytes, which this PR removes. That snapshot reads the recording into memory before the save, so it keeps the same 1 GiB as its own named in-memory budget:AgentPaneRecordingSnapshot.maximumPinnedBytes/maximumPinnedTotalBytes(AgentPaneRecordingSnapshot.swift,AgentPaneDroppedPaths.swift:58,AgentPaneRecordingDropTests.swift:167). Ordinary files dropped into a local chat still go as links to their real paths, with no cap.Attachment cap guard (
scripts/cmux-next/check-attachment-caps.py): it still exempted the caps this PR removes (Cloud, peer, decoder, CLIMAX_FILE_BYTES). The new testtest_retained_bounds_name_only_symbols_their_file_still_declares(tests/test_attachment_caps_guard.py) fails while an exemption names a cap its file no longer declares. Those exemptions are now gone, so a removed cap can't come back unreviewed.One staged-upload store (
CmuxNextAgentPane/AgentPaneStagedUploads.swift): each App stager kept its own stages, batches, expiry deadlines and cleanup tasks. That added +131 CmuxNextApp lines, which the pre-push arch ratchet refuses, plus a static mutable singleton.AgentPaneStagedUploads<Target>now owns all of it once:DemandTimerfor abandoned stages and batches (the peer stager used to sleep a task per stage);Each stager keeps only its own target checks, file layout and transfer. The peer store lives on
CloudService.peerUploads, besideuploads. Net effect: CmuxNextApp is -48 lines and has 2 fewer global mutable statics.Review of this carry (Astra, BLOCK at 6ff0fa8), fixed in f6edd0e:
CloudAccessCLI.uploadnow sends each file in its own call under an id of the app's. A failure or cancel removes every file of that upload (removeUploads) before it answers.removeUploadsnow tries every id and throws after all of them, and the undo test comparesundonebefore and after each wrong-target cancel.Review: codex gpt-6-astra (high, fast) SHIP at 9d4b5d8 (after a conflicting base merge with #27012, #28360 and #27768), receipts in the comments.
Not in this carry: findings 3 (
backend/apps/home-server/src/fs-bucket.ts:345,/dev/fd/<dir>/childon macOS) and 4 (cmux-tui/crates/acpmux/src/cli/cloud_join.rs, a repeated join resettingmeshRelay). Both are real on base, but this PR doesn't change either file. They came in through base merges (#26673 and the acpmux join work), so each goes in its own PR.Overlap: open #27200 (cap guard / AgentChatUploads), #26958 and #27768 (
intakeFiles.ts) change some of the same files for other fixes; whichever lands second takes a base merge.Verification
python3 tests/test_attachment_caps_guard.py: the new test was red at 472d2cd (9 stale exemptions) and is green at the head (14 tests).python3 scripts/cmux-next/check-attachment-caps.py: clean.python3 scripts/cmux-next/prepush-gate.py: no new violations. arch-ratchet reports app_lines -48 and global_mutable_statics -2.AgentPaneStagedUploadsTests(begin of any size, batch order and single use, chunk ownership and order, cancel during transfer stops it before the file goes). The original'sCloudUploadsTests,AgentPeerUploadsTests,ProcessRunner*Testsanduploads_tests.rsare kept.Observed / Not run
uploads_tests.rsandscripts/ci/proofs/upload-cancel.pyrun through CI.No capture: no UI change. The only visible change is that a large file now uploads where it used to be refused; the composer chip is the same.
Changelog
risk: normal (upload paths; no auth or relay policy change).
Agent session: agent session
🤖 Generated with Claude Code