Cloud: cmux cloud start-chat --from-folder copies a Mac folder the composer's way
Leo: "you pick a big folder, you pick a small folder, you pick a folder that has an existing kind of, like, Codex workspace type thing ... you should be able to see all that and then be able to do stuff based off that."
What
Only the agent pane composer could start a Cloud chat from a folder on this Mac. cmux cloud start-chat and cmux cloud start-session could start one only in a folder already on the machine. This adds --from-folder PATH to both. The folder is copied to the machine exactly the way the composer copies one, and the chat starts in that copy.
- One copier. The composer's copy (
cmux link start-chat copy: seeded from its GitHub remote, else copied) moves intoCloudStartChat.copyFolder, which the composer andstart-sessionboth call. The hand-off's folder check moves intoAgentPaneCloudStart.checkedFolder, whichAgentPaneHandOffandstart-sessionboth call. The composer behaves the same. - The same refusals. PATH must be an absolute, existing folder: never the home folder or above it, never the disk root, never a credential folder (#35019).
--from-folderwith--cwdis a usage error. A copy of a folder this Mac trusts is trusted on the machine (#31106). - A retry never overwrites the chat's work. The copy key comes from the machine and the canonical folder path, so running the same command again on the same machine reuses the finished copy (the ledger's existing rule).
- Agent starts follow #34994. The copy runs inside the start, so an agent's start still asks nobody by default, or shows the card when that setting is off.
--from-folderskips the relay-first chat (#35829), which cannot copy a folder.cmux cloud start-chat --helplists--from-folder PATH.
This also moves some code without changing it, so that no file over its size limit grows (CmuxNextApp may not grow):
CloudStartSessionStringsand its strings move to CmuxNextCloudUI.- The relay-chat code moves to
CloudStartSession+RelayChat.swiftandcloud_start_chat/relay.rs.
Tests
- Rust,
cloud_start_chat/tests.rs:- parsing and canonicalizing the path;
- relative, missing, file, missing-value and
--cwdcombinations refused; - forwarded to start-session with no relay call;
- the retry command and the pending message.
- Swift,
CloudStartSessionFolderTests:- request parsing;
- refusals for a relative path,
--cwd, a too-broad folder and a credential folder; - the copy key is stable for the same machine and folder;
- the run key differs per folder;
- the catalog argument;
- the shared check against a temporary Git folder;
- every origin (this Mac's CLI, a socket caller saying
cliwith or withoutcaller.terminal_id, a script, an agent) copies, and only to an owned machine (requireOwned); - credential folders are refused for every origin;
fromFolder's origin policy is held tocloudFileWrite's, so the two cannot drift.
- Rust,
link/sync_cli: a plain copy leaves credential folders and files out at any depth; the Rust folder list equals the SwiftsecretFolders(the test reads the Swift source); a folder swapped for a symlink mid-walk is not followed.
The cmuxterm-hq start-matrix probe has three Mac-folder rows (a small folder, a big folder and one with a GitHub remote) that use this on production once a Nightly has it.
Threat model
--from-folder copies a folder from this Mac to a Cloud machine. It takes every origin that may already write to that machine, and no origin gate:
cloudExec(Packages/macOS/CmuxNext/Sources/CmuxNextApp/Handlers/CloudHandlers+Machine.swift, line 46) runs any command on a machine the account has, for a CLI, agent or script origin, through the Cloud API's exec.cloudFileWrite(same file, line 94) writes any file contents there, for every origin.
A caller that can do either can already copy any file this Mac lets it read, so refusing --from-folder for those origins protects nothing, and it broke the one-command start Leo asked for ("testing it, like as a single command line argument, in conjunction with the arguments of folder or no folder"). An earlier revision refused every origin but the person's in-app gesture, then CLI script origins; both are reverted for this reason. CloudStartSession.fromFolder has no origin check, the same as cloudFileWrite, and theFoldersOriginPolicyIsCloudFileWrites fails if either one gains a gate the other lacks.
What protects the copy:
- An owned machine only.
CloudStartSession.requireOwned(CloudStartSession.swift) refuses unless the machine's link answeredvm_kind: "owner"for this account; a teammate's machine would keep the copy after refusing the chat. - Checked on disk first.
AgentPaneCloudStart.checkedFolderruns before any machine is made or taken: an absolute, existing folder, never home or above it, never the disk root. - Credentials never go. One list,
AgentPaneCloudFolderRefusal.secretFolders(Swift) =skips.rsCREDENTIAL_FOLDERS(Rust, held equal by a test): a selected folder that is, holds or sits under one is refused for every origin, and a copy leaves those folders out at any depth, along with credential files (.netrc,.npmrc,.pypirc,.git-credentials,.pgpass,.config/hub) and the existing.env*and key-file skips. - No symlink escape. The plain-copy walk opens each folder with
openat(O_NOFOLLOW | O_DIRECTORY)from its parent's descriptor and lists it through that descriptor, so a folder swapped for a symlink mid-walk is never walked through.
Not covered here: file reads at push time still open by path (O_NOFOLLOW on the last name only), and the git path's walk of .git is path-based.
Changelog
- Added:
cmux cloud start-chat --from-folder PATHstarts a Cloud chat in a copy of a folder from this Mac, the way the composer does.
Observed / Not run
Fleet results are in the comments below. Not run: a live start-chat on a tagged build.
Agent session: agent session
š¤ Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith Ā· Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Adds --from-folder PATH to cmux cloud start-chat and cmux cloud start-session so a Cloud chat can start in a copy of a folder on this Mac, copied the same way the composer's Cloud start copies one. Only the composer (the person's own run in the app) copies today: a command cannot prove it is the person's, so both verbs refuse --from-folder for now.
- The composer's copy and folder-check logic are factored into shared code both paths call; the composer behaves the same. The base merge keeps this while folding base's newer relay-chat code (login refusal codes, typed errors) into the moved files and sharing the login-refusal mapping between starts and relay chat.
- PATH must be an absolute, existing folder: never home or above it, never the disk root, never a credential folder (
~/.config/gh,~/.cargo,~/.gem,~/.terraform.d,~/.password-store);--from-folderwith--cwdis a usage error. The folder is checked on disk before any machine is made or taken. A copy leaves out the App's credential folders at any depth (the refusal list:.secrets,.docker,.cargo,Library/Keychains,.config/gcloud, ...) and credential files (.netrc,.npmrc,.pypirc,.git-credentials, ...), and each folder is opened by descriptor so a folder swapped for a symlink mid-walk is not followed. - The copy key comes from the machine and the folder, so re-running the same command reuses the finished copy instead of overwriting it.
--from-folderskips the relay-first chat, copies only to a machine this account owns, and only on the person's own run in the app (a gesture, never a socket request): an agent's start, any command (cli or script), or a socket caller ā even one claiming the person's terminal, which an agent in that terminal also claims ā is refused.- The copied folder's trust comes from the app link's owner answer; string and relay-chat code moves between packages to keep
CmuxNextAppwithin its size limit.