Remote chats start in the machine's work folder; refused starts offer one-click actions
Leo, 2026-10-31, after "Couldn't start Claude Code: Open a terminal in a folder on fs-firstsend-exercise first; the chat starts there.": "let's work on improving this a LOT".
What it does
B. A chat on a remote machine with no folder no longer dead-ends.
- cmux-tui adds the verb
agent-session-folders {surface}(capabilityagent-session-folders-v1). It answers{default, folders}.defaultis the machine's work folder,$HOME/work; on a Cloud VM it is created (0755) if missing, and on a Mac or SSH host nothing is created.foldersis the work folder and its direct subfolders, repositories first, never hidden folders or symlinks. Callers are the same asagent-session-trust: a trusted local connection, on an agent tab of this store. A listed folder grants no trust; starting there still goes throughagent-session-start's machine-kind gate and the folder trust question. - Swift stays thin.
AgentTabStore.runRemoteStartfalls back to thatdefaultwhen the chat has no chosen, seed or workspace folder. The lookup uses its own short connection, so the start's owner-checked connection is used for the start alone. An older daemon still answersapp.folder_needed. - Choose folder on a remote chat lists that machine's own folders after its workspaces' folders, each once.
- Copying the folder stays the 0-to-1 Cloud send's job (
cloud.startChat), unchanged.
C. A refused start names the one-click way out.
- The failed start card (
SwitchNotice, "Couldn't start {agent}: …") reads the refusal's code (startRefusal.ts, keyed on the code, never the words). It offers New Cloud Machine foragent_session.team_vm_blockedand Choose Folder… forapp.folder_needed, beside Retry. Any other code shows Retry only, as before. - The copy changes:
teamVM: "%@ is a teammate's machine, so its owner starts chats there. Pick your own machine, or create one with New Cloud Machine."folderNeeded: "%@ has no work folder to start in. Use Choose folder to pick one there."- Other languages are marked needs_review.
- Since #29107, a machine's creator is never refused on their own machine, so this card only reaches people starting on a teammate's machine. "Ask the owner" / "Share with me" come with the grant flow (cmux-platform chat grants), in its own PR.
Screenshots
Gallery entry agent-pane.switch-notice, rendered in Chromium at 520 px.
| Dark | Light | |
|---|---|---|
| Teammate's machine | ![]() | ![]() |
| No work folder | ![]() | ![]() |
Viewed: the action button sits beside Retry in the card's existing actions row, with a gap, and Retry takes the secondary style. Nothing repeats per row. The plain refusal (no action) still shows only Retry.
Prior art
#28111 / #29046 (cloud.startChat, which picks the machine and copies the folder; untouched), #28686 (composer Cloud row; untouched), #26323 (VM boundary). The start-in-Cloud lane (lane/start-chats-in-cloud) sends home: true with no cwd and relies on this fallback.
Tests
- Rust (
agent_session_folders.rs): no home or no work folder gives no default; a Cloud VM creates its missing work folder and a Mac doesn't; the list puts repositories first and skips files, hidden folders and symlinks; aworksymlink is never the default. - Swift (
AgentTabRemoteStartTests): a seed with no folder starts in/home/cmux/work, through the real attach client against a socket daemon. The folder-list merge keeps the workspaces' folders first, each once. - Web (
startRefusal.test.ts): code-to-action mapping. Galleryagent-pane.switch-noticecovers the team machine, no folder, and plain cases. - Fleet:
cmux-ci compile-check --app --recipe-version 2at 820176a0: app passed (71 s), cmux-tui passed (38 s).- Rust fmt, clippy and tests: pass (130 core, 37 CLI).
- Webview bun tests pass (4/4 new plus the harnessSwitch tests). The webview
tscerror is intest/latency/probe.ts(interactionId), a file this PR doesn't touch.
Deploy: none. The app ships its own cmux-tui and pushes it to the VM.
Agent session: agent session
🤖 Generated with Claude Code



