cmux-next: folder trust never leaves an agent stuck (auto-trust, wait on a card, Needs trust row)
When an agent starts a sub-session in a folder nobody has answered the trust question for, the person ends up hunting for a button or retrying by hand. Claude Desktop does this: the Trust button sits in another session's UI and the agent can't see the click. In cmux NEXT the start skipped the gate entirely. This PR makes trust something the person never has to hunt for.
Risk: high, folder-trust boundary and Web/relay gate. The repo has no risk:* labels, so this line stands in for one.
Auto-trust, opt-in. The person can trust their own folders without being asked. A folder qualifies when it is under one of their project roots, or when every GitHub remote of its repository belongs to one of their own users or orgs. The settings are in Settings > Agents > Folder Trust: agents.trust.autoTrust, agents.trust.projectRoots and agents.trust.ownOrgs.
- Off by default, with empty lists. Nobody's folders or owners are built in; the person turns the toggle on and names their own.
- Explicit answers win. It only turns an unknown into trusted: an explicit Don't trust and an agent's own untrusted level still win.
- Home is never trusted wholesale. Symlinks are resolved first, and neither home nor
/can be a root. The org rule reads only a repository strictly inside home, so a dotfiles~/.gitdoes not make every folder in home the person's own. - Fails closed. A settings file that can't be read turns the rules off, and agents can't change the keys (
.userOnly). - Pane only. It feeds
acp.trust.get, so the pane's own question skips the prompt too. It never answers for a Web client (see Access).
A card, not an error. start_session in any other folder raises a Trust / Not now card on the starting session through the question-card path (#21962), so it renders in the chat thread with the same keyboard handling.
- Resumes by itself once the folder becomes trusted, whether the card was answered or the folder was trusted elsewhere (
acp.trust.setwakes the wait). - Not now refuses with
subsessions.trust_declinedand is remembered for ten minutes, so a retrying agent isn't met with the same card. - Thirty minutes without an answer refuses with
subsessions.trust_timed_out. - Each start owns its card. When a start ends in any way, including a dropped tool call, it withdraws only its own card, matched by
toolCallId, never another start's card for the same folder. The task that raises the card is aborted, and the card is withdrawn again once that task has stopped, so a card raised late cannot outlive the wait. - Worktrees inherit trust. A worktree made from a trusted folder is recorded as trusted.
Sidebar. While it waits, the starting session carries the tag trust.pending:<name> and its row reads "Needs trust" in the question card's amber instead of "Needs your input". Two same-name starts share the tag until the last of them ends. The child has no session until the folder is trusted, so the state sits on its starter's row.
Access. No new socket method or allowlist entry.
- Who can answer the card: only the person (the existing decision-proof rule). A Web connection is refused (
trust.web_cannot_answer), asacp.trust.setalready is. - The Web/relay gate counts only a person's decision.
server/trust_gate.rspasses an auto-trust rule only for the app's pane. ForOrigin::Web, or a peer forwardingvia: web, the folder needs a decision a person made, or an agent's own trusted level (trust::web_session_level). That applies to session/new, prompts at the guard and again at dispatch, prewarm and handoff. So a remote browser gains nothing from auto-trust. - Forging a remote needs local file access. The org rule reads the repository's
.git/config, which a clone does not ship. Writing it needs local file access, which already lets an agent edit the settings file itself.
Review fixes (backlog-sweep's BLOCK, 6 findings).
- (1) Defaults are off and empty.
- (2) The repository search stops below home.
- (3) The Web gate needs a person.
- (4) A start cancels only its own card.
- (5) A late card goes away with its task.
- (6) New tests:
subsession_trust.rsstep 6: one start's Not now leaves the other start's card up.- Step 7: Don't trust wins over a root.
- Step 8: a Web
session/newunder a project root getstrust.pendingwhile the pane starts. - Step 9: an unreadable settings file trusts nothing.
trust_auto.rs: the defaults trust nothing, and a dotfiles repository at home trusts nothing.- Swift
TrustSettingsTests: the defaults are off and empty.
Deviation: the toggle is a regular Settings row rather than a Debug Settings toggle, because the daemon reads the settings file and can't see Debug defaults. It defaults off, which keeps this high-tier behavior behind a setting.
Existing work checked: #22471 (open) refuses sub-session children in untrusted folders and rewrites the same start path; this lands independently and its trust walk can sit behind ensure_folder_trusted. #19045 only added gallery coverage for the pane's folder question.
Changelog
Added: An agent's sub-session in a folder you haven't trusted waits on a Trust / Not now card in its chat; you can turn on trusting your own project folders and GitHub owners without asking (Settings > Folder Trust).
Observed / Not run
Original author ran: bun test of question/trust.test.ts (5 pass), web vp check on changed files, acpmux-protocol:check, localization_catalog.py check, gen-settings-sources.py --check, prepush-gate.py.
Review fixes (paired-approvals lane, pushed through the GitHub API because local git on cosmic-gray was stalled):
- Ran: regenerated
SettingsSchema+Trust.generated.swiftwithgen-settings-sources.py;rustfmt --checkon the changed Rust files; JSON validity oftrust.jsonandacpmux-schema.json. - Running: focused
cmux-tuirun, test filtertrust: https://github.com/manaflow-ai/cmux/actions/runs/39535073001 - Not run locally (agents never build here): cargo test or clippy, and the Swift settings tests. CI is the first compile.
- Not covered by a test: the 30-minute timeout path (the wait is a constant) and a start dropped mid-wait (a closed connection does not cancel a running request). Both use the same guard that step 6 exercises.
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes folder trust so an agent's sub-session in a folder nobody answered for never leaves the person hunting for a button or the agent stuck.
Folders under the person's project roots, and repositories whose GitHub remotes all belong to the person's own users/orgs, are trusted without prompting (Settings > Agents > Folder Trust: agents.trust.autoTrust, agents.trust.projectRoots, agents.trust.ownOrgs). The switch is off by default with empty lists, so nothing is trusted by a rule until the person names their own. An explicit Don't trust and an agent's own untrusted level still win; symlinks are resolved first, and the org rule ignores a home folder at /; a settings file that can't be read turns the rules off; agents can't change the keys (.userOnly). The rules feed acp.trust.get, which reports which auto rule answered (root or org).
Trust card
start_sessionin any other folder raises a Trust / Not now card on the starting session and waits; it resumes by itself when the folder becomes trusted, from the card oracp.trust.setanywhere. Starts on the same folder share one card.- Not now refuses with
subsessions.trust_declinedand is remembered for ten minutes; thirty minutes without an answer refuses withsubsessions.trust_timed_out. - A withdrawn card (trusted elsewhere, a second start, a timeout, a dropped start) is settled like an answered prompt: it logs a cancelled decision and restores the session's status through the same path an answer uses, never twice.
- A worktree made from a decision-trusted folder is recorded trusted; a rule-trusted folder gets no record, because a Web client reads a record as a person's answer.
- While waiting, the starting session's sidebar row reads "Needs trust" instead of "Needs your input"; only the person can answer (
trust.web_cannot_answerfor a remote browser), and a Web client's warm, prewarm and Web turns take no auto-trust rule as an answer.
Agent session: cosmic-gray
🤖 Generated with Claude Code