Skip to content

Require local Allow for Phone Access on this Mac - #25437

Merged
teamleaderleo merged 40 commits into
feat-cmux-nextfrom
leo/phone-access-admission-1026
Nov 1, 2026
Merged

teamleaderleo merged 40 commits into
feat-cmux-nextfrom
leo/phone-access-admission-1026

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Leo, asked "should Phone Access ask on this Mac before serving a new phone on your account?", answered: "probably? you can decide". Decided: yes, an unfamiliar phone waits for a local Allow on this Mac.

Leo's words for the iOS/remote push: "well i just think we can improve it a f***ton more than it is now... it's slow it's bad it belabours the point in too many tooltips it's ugly in places it doesn't even work in places there's lots of functionality that needs to get in"

What changes

Phone Access currently serves same-account MobileIrx peers without a local decision on this Mac. With this PR, an unfamiliar TLS-proved phone endpoint waits for a person's Allow on this Mac. The decision is saved in the shared per-user/per-Mac phone list, and removing the phone in Settings > Mobile closes its sessions and makes it ask again.

  • The question shows the signed-in account and the full device key. Allow, Don't Allow and Cancel are buttons. Only Don't Allow starts a 10-minute cooldown; Cancel, Escape, dismissal and the 2-minute timeout leave the next connection free to ask.
  • One question is shared per endpoint, at most two questions are pending, and debug automation can't answer it.
  • Removing a phone cancels its open question and invalidates a late Allow, per phone. A store change that began before a removal can't re-approve the removed phone.
  • Account switches invalidate trust synchronously. Allow is published only after a successful save.
  • Legacy stored entries decode as hostd. Iroh entries never reach hostd's devices, credentials or Face ID approvers. Settings > Mobile lists and removes Iroh phones even when the legacy host is off.

This adds a local Allow gate to the existing Iroh phone transport (MobileIrxHost, already on base). It adds no new Iroh machine-to-machine use.

Credits the intent of #21898 and the sole-user ownership/question-binding fix from #23552. From #21898:

This adds the Codex-style per-device step for phones, and Settings > Mobile becomes the one place that lists every phone allowed on this Mac.

Screens

Before: no prompt; a same-account phone is served with no decision on this Mac.

After (dark, 2x, synthetic device and account), from PhoneAdmissionCaptureTests on a fleet Mac at this head's app code. I viewed it before landing:

Allow Demo iPhone to use this Mac? dialog

The light capture draws the shared dialog's glass-layer title and buttons blank in an offscreen render. That happens in the existing CmuxDialogView, which this PR does not change, so it isn't shown here.

Validation at 49bcf768a371a604a541b607b0ee3e551905c3f7

  • Fleet app compile: passed at fc52c2a3 (the last commit only adds two entries to scripts/localization-allowed-omissions.json).
  • Fleet suites at fc52c2a3, all passed: CmuxNextMobileHostTests (44 tests, 10 suites) and CmuxNextMobileTests (93 tests, 18 suites), which include PhoneAdmissionTests, PhoneAdmissionCaptureTests, PhoneAdmissionStoreTests and PhoneAccessGateTests; PhoneAccessMigrationTests and PhoneAccessSettingTests in a separate run.
  • The six Phone Access suites also passed with a GUI session (same app code, at 88d80eca).
  • The localization debt ratchet failed at fc52c2a3: the new labels "%@ · cmux next" and "%@ · cmux (iOS)" copied English into 8 locales each. 49bcf768 lists them as brand literals, so IPhone.xcstrings stays at its baseline of 19.
  • Independent reviews: SHIP at 88d80eca (full PR, including both earlier BLOCK findings), f016131 (capture test), fd115828 and fc52c2a3 (hand-resolved feat-cmux-next updates; both sides of each conflict are kept), and 49bcf768 (the localization entries).

Changelog

Added: Phone Access asks once on this Mac for each unfamiliar phone, and Settings > Mobile can forget it.

Agent session: agent session

🤖 Generated with Claude Code

Re-cut the per-Mac phone admission intent from #21898 and the sole-user question binding from #23552 on current base. Hold for Leo’s product decision. Preserve hostd phone credentials and exclude Iroh entries from approval authority.

Agent-Session: https://cmux.com/deeplink/agent?id=aid-fa0019a44432440c6217
@teamleaderleo teamleaderleo added the risk:high Risk tier (docs/ci/risk-tiers.md) label Oct 26, 2026
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review b2a476b: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr25437 -f ref=leo/phone-access-admission-1026 -f platform=macos -f backend_mode=local

Gallery before/after for b2a476b: 16 states rendered, 2 images differ, 261 more touched states not rendered. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 41252887871 -R manaflow-ai/cmux -n gallery-advisory-25437-b2a476b424133813420d830508c8d17be5322187 (then read gallery-review.json)

States
State Theme Status Diff Picked by
pages.settings/general Apple System Colors Light unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general Apple System Colors unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-customized Apple System Colors Light unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-customized Apple System Colors unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-1 Apple System Colors Light unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-1 Apple System Colors unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-2 Apple System Colors Light unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-2 Apple System Colors unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-3 Apple System Colors Light unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-3 Apple System Colors unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-4 Apple System Colors Light unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-4 Apple System Colors unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-5 Apple System Colors Light unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-5 Apple System Colors unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-6 Apple System Colors Light unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings/general-group-6 Apple System Colors unchanged 0% imports MobileCard.tsx (1 hops)
pages.settings-initials/profiles Apple System Colors Light unchanged 0% same-folder MobileCard.tsx
pages.settings-initials/profiles Apple System Colors unchanged 0% same-folder MobileCard.tsx
pages.settings-initials/machines Apple System Colors Light unchanged 0% same-folder MobileCard.tsx
pages.settings-initials/machines Apple System Colors unchanged 0% same-folder MobileCard.tsx
pages.settings-interface-size/default Apple System Colors Light unchanged 0% same-folder mobileCard.test.tsx
pages.settings-interface-size/default Apple System Colors unchanged 0% same-folder mobileCard.test.tsx
pages.settings-interface-size/enlarged Apple System Colors Light unchanged 0% same-folder mobileCard.test.tsx
pages.settings-interface-size/enlarged Apple System Colors unchanged 0% same-folder mobileCard.test.tsx
pages.settings-menus/generated Apple System Colors Light unchanged 0% same-folder MobileCard.tsx
pages.settings-menus/generated Apple System Colors unchanged 0% same-folder MobileCard.tsx
pages.settings-menus/customized Apple System Colors Light unchanged 0% same-folder MobileCard.tsx
pages.settings-menus/customized Apple System Colors unchanged 0% same-folder MobileCard.tsx
pages.settings-menus/terminal Apple System Colors Light unchanged 0% same-folder MobileCard.tsx
pages.settings-menus/terminal Apple System Colors unchanged 0% same-folder MobileCard.tsx
pages.settings-motion/play-search Apple System Colors Light changed 0% same-folder MobileCard.tsx
pages.settings-motion/play-search Apple System Colors changed 0% same-folder MobileCard.tsx

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

Passes: CI passes on b2a476b424.

CI passes on b2a476b424 (run 41252888002 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

web and size checks: green

Default changes

This pull request changes 3 defaults at b2a476b42413 (what everyone gets without configuring anything):

  • tunable-source.CmuxNextApp/Control/DebugDialog.swift:userOnly: "4c35859d9a7e2a74d3a89e47aab400110db51ecf4df8b4ac13d9e292031be58b" -> "520db8f42a15ed84f881e5706b276e137c46462d12e3c80e3fdea6f57729825f"
  • tunable-source.CmuxNextMobileHost/PhoneAdmission.swift:dialogIdentifier: (none) -> "165f5a5f2545aad586bfd7b8f25cec41f110952c10add7e1e51174661588d3a6"
  • tunable-source.CmuxNextMobileHost/PhoneAdmission.swift:phoneAccessDialogIdentifier: (none) -> "56f2a98044b9262b4b4547affc11f061b97cab347ef6a0794da0150bc4422fbb"

Signed off on this commit by review (codex gpt-6-astra high).

Merge receipt: merged as b5d1dbf0bd12 with these defaults changed, signed off by review (codex gpt-6-astra high).

Regenerate the snapshot with python3 scripts/ci/defaults_guard.py snapshot.

Shipped in:

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-43q9t created for this PR by the cmux coordinator.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Orphaned: no activity for 13 h, and no lane holds a lease on it. The orphan sweep could not merge feat-cmux-next in: conflicts in Packages/macOS/CmuxNext/Sources/CmuxNextApp/Control/DebugDialog.swift. Listed for a lane to resolve.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

For awareness: #27297 (iOS host wake) adds an independent wake observer/control line and a NEXT keep-awake reason in the same MobileHostSupervisor/IPhoneHostService files. It doesn't touch admission or credentials, so this PR's local Allow gate is unaffected. When merging, keep both initializer additions.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · took over from an earlier session · session

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Independent review (codex gpt-6.1-sol high; Astra via SR2 hung): BLOCK at c260dfa

Summary: Adds per-user persisted phone admission and gates Iroh sessions on a local Allow.

Findings, most severe first:

  • IPhoneHostService+Pairing.swift:198-205, MobileHostPairedStore.swift:74-99 — removal during an async load is lost. Sign-in clears current while attach/reload runs. If the user removes a phone then, change sees current == nil and throws; the later reload publishes the old file, re-trusting the removed phone. supervisor.drop only closes existing links, so the phone can reconnect without another Allow.

  • MobileHostPairedDevices.swift:196-201 — raw JSON is trusted across relaunches. Any valid file with the matching userID, including an older restored copy after removal, becomes trusted without a new local Allow. The current feat-cmux-next tip adds a pairing seal specifically to prevent this.

The current tip also materially rewrites this area and removes/replaces several PR-added files; the three-way comparison reports conflicts in the touched AppDelegate, pairing, supervisor, device-list, and service files. git diff --check is clean; no builds or tests were run.

@github-actions github-actions Bot added risk:normal Risk tier (docs/ci/risk-tiers.md) and removed risk:high Risk tier (docs/ci/risk-tiers.md) labels Oct 30, 2026
@teamleaderleo teamleaderleo changed the title [HOLD] Require local Allow for Phone Access on this Mac Require local Allow for Phone Access on this Mac Oct 30, 2026
teamleaderleo and others added 3 commits October 30, 2026 12:17
Keep removals and sealed writes ordered through the paired-device queue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-006fc1b94bdcbe5a7e87
Route Iroh removals through the synchronous admission marker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-006fc1b94bdcbe5a7e87
Exercise removal during load and restoration of older sealed bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-006fc1b94bdcbe5a7e87
… that ended

Turning Phone Access off cancels its open questions one hop later. If it came back on first, that hop cancelled the new period's questions too; an account switch's reconciliation likewise cancelled questions asked since the switch. Each now cancels only questions from an ended serving period or an earlier account, and the per-phone admission tokens keep counting up instead of resetting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 31, 2026
… tests only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An account switch is already settled by the generation check (its questions
cancelled, the listener restarted), so its removal event no longer runs the
removal loop. A real removal stops as soon as another account is selected,
including while a disconnect is suspended, so it cannot cancel or disconnect
the new account's phone. Store change events now carry their account
generation and whether they are an account switch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Account switches rely on the listener restart to end the old account's phone
links, but stopHost() fired its teardown and forgot it, so a quick sign-out and
sign-in (or off and on) could start the new listener while the old one still
served. The teardown is now kept and the next start waits for it.

Also fix the new account-switch test's capture of a var in a Sendable closure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo and others added 2 commits October 31, 2026 15:26
A start that a quicker one cancelled still had its old listener stopping
inside the cancelled task, outside `stopping`, so the next listener could
start beside it. Every teardown, from a stop or a restart, now goes through
one chain that each new listener awaits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
savePaired queued its store edit from a Task, so a Remove in Settings >
Mobile made in the same main-actor turn reached the queue first, and the
later approval started after the removal and could restore the phone.
MobileHostPairedStore.submit queues the edit before it returns, and
savePaired uses it, so the removal wins as it did before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 31, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high): SHIP at 252c251

The listener-teardown, account-switch cleanup, legacy approval/removal ordering and admission cancellation findings from earlier heads are resolved. A fleet app compile passed at this head.

Follow-ups:

  • Track the ask-task presentation.
  • Report live Iroh connection state instead of a hard-coded connected: false.
  • Document the approval/persistence boundary.

Phone rows now stay listed while the host is off so they can still be
removed, so the step that checks the card turned off now passes no phones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 31, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot 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.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high): SHIP at b2a476b

The listener-teardown, account-switch cleanup, legacy approval/removal ordering and admission cancellation findings from earlier heads are resolved. A fleet app compile passed at 252c251; this head adds only a webview test fixture fix (phone rows stay listed while off).

Follow-ups:

  • Track the ask-task presentation.
  • Report live Iroh connection state instead of a hard-coded connected: false.
  • Document the approval/persistence boundary.

@teamleaderleo
teamleaderleo merged commit b5d1dbf into feat-cmux-next Nov 1, 2026
190 of 194 checks passed
@teamleaderleo
teamleaderleo deleted the leo/phone-access-admission-1026 branch November 1, 2026 00:03
lawrencecchen added a commit that referenced this pull request Nov 1, 2026
…se-quit-flush

Port of #28275 onto feat (lead decision 05:20Z), keeping feat's state-file and
quit work and adding only what this PR adds beyond it:
- AgentUnsentDrafts: feat's #28204 undelivered drafts and unreadable-file
  set-aside, on this PR's per-app AgentPaneStateFiles (no static registry).
- QuitCoordinator: feat's #22919 QuitStepLimiter, time-left reply margin,
  launch marks and engineCloseWait, plus this PR's drains of the state files
  around the engine close, the testable quitSteps() (was steps(), which now
  names feat's limiter) and closeEngines seam, and the deadline end that records
  cut-off files.
- AppDelegate/AppServices: feat's openedURLs, windowRecord, peer approver and
  injected key window; this PR's secondSignalExit (no blocking flush in prepare)
  and the state-files owner and loss record.
- Paired phones: feat's #25437 MobileHostPairedStore now owns the directory's
  one MobileHostPairedDevicesFile (configure(directory:lastQuitCutOff:
  cutOffSettled:)); the iPhone host takes it from the store for the quit watch,
  so the shared-list transactions and this PR's cut-off accounting use one file
  object and one queue (an instance enqueue that counts as saving), not two.

This branch was previously deployed

1 inactive deployment
ci-routing-agent — b2a476b4 Deployed Oct 31, 2026 by teamleaderleo via cmux-tui tree preflight #92057
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant