Repository navigation
Settings > Devices: each paired phone gets its own acpmux credential and the Full access switch (off by default) - #25088
Conversation
…switch of its own Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red
…and the Full access switch Leo: "#2 seems reasonable i think... it's a gate that you can enable for your phone, which is an attack vector you opt in to". - The iPhone host's approver sync (its proven person line) issues each paired phone its own acpmux credential (pair_issue role phone) and hands hostd the set (phone_credentials); a phone no longer paired has its record revoked at once, and each new connection revokes phone records no paired phone owns. - cmux device list shows a phone pairing as kind phone with its setting; Settings joins it to the phone's row (by kind and name), so the phone row gets the same Full access switch, off by default, and its record never shows as a row of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red
…d the page test covers it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red
|
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)
Unclear: CI failed on
Failures:
Matched log linesNot re-run automatically: Written by red: web / Web status; NEW: green on the base 5251e6b; do not merge until green; see lane-rules Gallery before/after for
States
Pick other states with a |
|
Owned by hb-acpmux on big-red · session |
|
Found 13 test failures on Blacksmith runners: Failures
...and 3 more test failures. View all on Blacksmith
|
|
Bead |
…n unlisted phone gets no credential, and revocations hold until confirmed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red
…ets a credential, and revocations retry until acpmux confirms them Review findings on #25088: the row used the phone's first-sorting id (possibly its presence id), so it found no record; a phone removed while the app was stopped stayed paired locally and was issued a credential with no row to switch it off; and a revocation lost with its connection was never sent again. MobileHostPhoneCredentials keeps the credentials as a value: phones come from the paired phones the account lists, the first connection lists acpmux's phone records even with nothing else to sync, and a revocation stays pending (and asks for a connection) until acpmux answers it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red
…d record to end, a lost issue is found by the next list, a refusal retries, and an unread account list revokes nothing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red
… a re-pair, a lost reply or a refusal never leaves a record live Round-2 review findings on #25088: - A phone that leaves is removed until a list, taken after its last issue and revoke, shows no live record of its name; only then can it get a new credential, so pairing again never inherits the old setting, and an issue whose reply was lost is found by the next list. - Issues wait for the connection's list. - A refusal drops the connection, and the backoff retries it. - Until the account's list is read, no record is revoked for being unlisted, so a saved setting survives the launch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
|
…s listed, a phone listed again waits for its unlisted record, and connections off end the records Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red
…nections off ends them Round-3 review findings on #25088: - An unlisted record being revoked blocks a new credential under its name, the same as a phone that left, until a list shows it gone. - A removal with nothing under way owes a list at once. - With the setting read off, no phone may hold a credential, so every phone record ends. Before the setting or the account's list is read, which phones may is still unknown, and nothing is revoked for being unlisted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16c8c-54ec-71b2-a0fa-125d1404dd72&host=big-red
|
|
|
Shipped in:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Follows #25078 (merged, squash 5251e6b), which gave a paired phone its own acpmux credential (role
phone) for the per-device Full-access setting of #25038. #25069 added the Settings > Devices switch for paired machines. This PR gives every paired phone a credential and puts the same switch on its row.The ask
Leo, relayed by claude-sr2: "#2 seems reasonable i think... it's a gate that you can enable for your phone, which is an attack vector you opt in to".
claude-sr2's build order: a per-device setting, off by default, set from the Mac; revoking it takes effect at once; remote_guard is unchanged for a device without it; a test proves a device without the setting is refused.
Before / after (Settings > Devices, the page's mock data)
Captured headless (Playwright Chromium, on the fleet) from the Settings page's own dev server and mock provider, at 2x. Before is the mock as base sends it, where a phone row has no switch. After is the row the app now sends for a phone with its own record. The native Settings window was not captured, because Swift is not run on agent hosts. The page is the same React page the window shows.
No capture: the native files here carry no UI of their own. They are the phone credential plumbing: approver sync, credentials, hostd's line, and the directory join. The one visible change is the Settings > Devices row, which is a webview page, captured below from its own dev server. A fleet
cmux-ci capturetour cannot show that row, because the row only exists for a phone paired with this Mac that has its own acpmux record.What changed
MobileHostApproverSync, on the connection it already proves as the person to set approvers):MobileHostPairedDevices.listed(in:)). Those are the phones that have a row, and so a switch. Until the first directory read succeeds, no phone gets one._acpmux/pair_issue {name: "phone-<link id>", role: "phone"}for each of those phones. acpmux replaces that phone's old record and keeps its setting.MobileHostPhoneCredentials, which is unit-tested.setPhoneCredentials, then hostd'sphone_credentialsstdin line. They are kept in memory only and sent again to each new hostd.pair_revokeputs its chats back in their policy's mode before it answers (acpmux: a paired machine's Full-access setting (off by default) lets it drive a remote chat in Full access #25038).pair_listis the source of truth. A phone that leaves, and an unlisted record, are "ending" by record name. Each live record of that name is revoked, at once when its id is known. No credential goes under that name until a list, taken after its last issue and revoke, shows no live record of it. So a new record never inherits a setting meant to end, and an issue whose reply was lost is found by the next list.cmux device list: aphonepairing is listed askind: phonewith itsfull_access.DeviceDirectory):phone:<link id>, the id it paired with, even when its presence id sorts first) carries that record's switch, off by default, with the same help line. The record is matched by kindphoneand its exact name, never by name alone.Tests (red first)
Red commit fcce648 adds the tests only. On base the Swift suites do not compile:
MobileHostLaunch.phoneCredentialsLineandMobileHostPairedDevices.acpmuxRecordNamedo not exist (fleet step c090acdc0056).AddDeviceFlowTests.aPairedPhoneHasTheFullAccessSwitchOfItsOwnRecordchecks:Each review round's fixes came with a red commit of their own: tests only, with the new API missing, so the Swift suites do not compile there. Round 1's tests are in a1856a6 (fleet step adb1fd8c03e0), round 2's in efebfce (step 9ad74ca20e9e), and round 3's in 9a0dd8e.
MobileHostPairingTests.aPairedPhoneIsItsLinkIDAndOnlyAListedOneGetsACredential: a phone's row and credential follow its link id even when its presence id sorts first, and a paired phone the account no longer lists gets no credential.MobileHostPhoneCredentialsTests(7 tests) cover:not_foundcounts as done.MobileHostTests.phoneCredentialsAreOneControlLinechecks the stdin line, and that the record name is valid for acpmux (at most 80 characters,[a-z0-9-]).cmux-tui
devices::tests::a_phone_pairing_is_a_phone_row_with_the_setting.Webview
hostSections.test.tsxchecks the phone row's switch and its{id: "phone:p1", on: true}call.Fleet:
cmux-ci run, package-test-lane): 4 suites passed at b079a57 (AddDeviceFlowTests, SettingsDeviceFullAccessGestureTests, MobileHostTests, MobileHostPairingTests). At the final head b094515, all 5 suites passed (fleet step 6852c06ed7b6), with CmuxNextMobileHostTests included (21 tests in 6 suites).devices17/17 and fmt clean.hostSections.test.tsx11/11 andvp fmt --checkclean.Review
Codex gpt-6-astra xhigh, an independent reviewer. It reviewed the acpmux and hostd half (#25078) to SHIP. On this half, rounds 1 to 3 were BLOCK:
Each round's findings were fixed with tests, above. Round 4 was SHIP at b094515.
Risk: this decides which phones hold an acpmux credential, and so which phones a Full-access setting can cover. It only narrows. A phone without its own record falls back to the dashboard token, which the phone filter already restricts to modes that ask.
Not in this PR
The iPhone's mode menu still offers only modes that ask (
ACPMode.phoneChoices). With the setting on, the phone already drives a chat in Full access: it can prompt a Mac-started chat in Full access, and switch modes through acpmux. Offering Full access in the phone's own menu (acpmux'sinitializenow saysfullAccess) is an iOS follow-up.Stop conditions
No new timer, observer or loop. The issue and revoke calls ride the approver sync's existing connection, its reconnect and its backoff. The credential set is replaced whole on every change.
Agent session: big-red
🤖 Generated with Claude Code