Cloud start: never copy a credential folder; a gone or home folder says why

Merged
TE
2d ago
+175-20

Follow-up to #34969, which fixed the Cloud start Leo asked about ("what's this") when it refused with:

This folder isn't open in this tab; pick it to send it to Cloud.

Since #34969 the Send's gesture is the pick of the composer's folder. That review left three follow-ups, and this PR fixes all of them.

Fixes #35006: never copy a credential folder

A Cloud start is now refused when its folder, its Git working copy or any of that copy's git folders:

  • is one of home's credential folders: ~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gcloud, ~/.kube, ~/.docker, ~/.secrets, ~/.azure or ~/Library (which holds Keychains);
  • is inside one of them; or
  • holds one of them (so ~/.config is refused because it holds gcloud's folder).

The match ignores case, because this Mac's disk does not tell ~/.SSH from ~/.ssh. The refusal names the folder: "Cloud never copies ~/.ssh, which holds keys or credentials; pick a project folder." There was no shared Rust or web helper on this path, so the rule is the same Swift check (AgentPaneCloudFolderRefusal). Its list matches cmux-tui's link/access_cli.rs SECRET_FOLDERS, which covers a different path (the files of a shared folder).

Fixes #35005: each refusal says why

These refusals used to share transport.path_outside_roots and its "isn't open in this tab" message. Each now has its own code and message:

FolderCodeMessage
Missing, or not a foldercloud_folder_missing"This folder no longer exists; pick another one to send to Cloud."
Home or above itcloud_folder_home"Cloud doesn't copy your home folder or a folder above it; pick a project folder."
Credential foldercloud_folder_secretNames the folder, as above.

The codes are in both refused-here lists (AgentPaneHandOff.refusedHereCodes in Swift, REFUSED_HERE on the page), so no checklist step is marked failed. The three new strings are in every locale of Localizable.xcstrings.

Fixes #35004: stale comments

The comments on gitFolders and CloudStartChat that still described the old roots check are fixed.

Tests

  • Swift AgentPaneCloudStartTests: aCredentialFolderOrOneHoldingItIsNeverCopiedAndTheRefusalNamesIt, aMissingFolderAndHomeOrAboveEachSayWhy.
  • Web cloudStartProgress.test.ts: "a gone, home or credential folder each says why".

Commands:

  • Swift: cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg suite --arg Packages/macOS/CmuxNext --arg AgentPaneCloudStartTests, at c2adee3 (the tests alone, expected red) and at the head (expected green).
  • Web: bun test src/agent-session/acpmux/cloudStartProgress.test.ts through cmux-ci test.

The results are posted in a comment.

Observed / Not run

  • Observed: prepush-gate.py shows no new violations, and the xcstrings key check passes. scripts/web-lint.sh fails only on test/transcript-row-hover-scope.test.ts:82, which this PR doesn't touch and which is red on base c48f7b6d29a.
  • Not run: a nightly Cloud start.

Changelog

  • Fixed: A Cloud start never copies a folder of keys or credentials (~/.ssh, ~/.aws and others), and says why when it refuses a missing folder, the home folder, or a credential folder.

Agent session: agent session

🤖 Generated with Claude Code


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


Summary by cubic

Cloud starts now never copy a folder of keys or credentials, and each folder refusal says why instead of the generic "This folder isn't open in this tab."

Bug Fixes

  • Refuses any folder that is, is inside, or holds one of home's credential folders (~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gcloud, ~/.kube, ~/.docker, ~/.secrets, ~/.azure, ~/Library/Keychains), matching case-insensitively and naming the folder in the refusal. Projects under ~/Library (iCloud Drive, CloudStorage) still start, and a pane with no home folder checks this Mac's.
  • Gives missing, home-or-above, and credential folder refusals their own codes and messages (cloud_folder_missing, cloud_folder_home, cloud_folder_secret) instead of the shared transport.path_outside_roots.
  • Keeps the new codes on the refused-here lists so no checklist step is marked failed, and fixes stale comments describing the old roots check.