Skip to content

Review record: object-sync slice 3 (host replica lease, Settings toggle, owner purge) - #22085

Closed
lawrencecchen wants to merge 21 commits into
feat-cmux-nextfrom
cmux-next-gate-1d91f6081e4a
Closed

lawrencecchen wants to merge 21 commits into
feat-cmux-nextfrom
cmux-next-gate-1d91f6081e4a

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Review record for object-sync slice 3 (hq-b5). This lands directly on feat-cmux-next (lane rule: no PRs into feat-cmux-next); this PR only carries the review and is closed with the landed SHA.

What

  • Daemon (cmux-tui): the host replica publisher asks the verified app for a new install token (cloud-host-replica-needed, expiring 120 s before expiry or unauthenticated), a renewal swaps the token in place, a refused token is asked for again every 30 s, expiring asks are paced, lease generations only go up. The published state is a projection: per workspace only id, resource_id, short_id, name, name_revision, group, color, icon, pinned, marked_unread, unread_count, active.
  • App (CmuxNext): new setting phone.cloudReplica in Settings > Accounts > Phone Access, opt-in until Design and product calls (gallery: #15427), plus CI admin #13742 decides the default, refused to agents (.privacy). CloudHostReplicaFollower leases the install token while the setting is on, the account is signed in, Cloud is allowed and the daemon serves cloud-host-replica-v1; off purges the cloud copy and keeps a user-keyed mark until the daemon confirms; sign-out clears; the ask mints a new token.
  • Backend: host.replica.purge {host} (TeamDO op): the owner deletes the cloud copy, also while the Mac is off. Owner only, no agents, not an orphaned host, audited; a non-tombstone drop purges HostDO's streams and closes its replica sockets.

Tests

  • Rust (rbx): cloud_host_replica 5 tests incl. the_daemon_asks_for_a_new_token_in_time_and_a_renewal_keeps_the_socket (red a822fb6 before the fix) and a_refused_token_is_asked_for_again_until_a_new_lease_comes (red e79823e: timed out waiting for the ask again); cloud_conversation 23 tests; cmux-cloud-wire tests.
  • Backend (Freestyle): test/host-replica.test.ts incl. lets the owner delete the replica while the host is off (red 19ad2f6: the op did not exist), team-member-remove.test.ts.
  • Swift (AWS fleet): CmuxNextSettingsTests (PhoneAccessSettingTests, exports), CmuxNextDaemonTests, and the gate's 65 CmuxNext suite runs.
  • GUI proof on cmux-lawrence-2 (nx-remote job 1019-090206-2d9cf7, fleet app 075c20c, tag objsync-l4, staging): with phone.cloudReplica on, a reader signed in as the same account sees host_92684b1c25de20d03b0b online with 2 workspaces ("Home", "workspace-1") and only the 12 allowed fields; after the app quits, the reader sees the host offline with the same tree.
  • Rust (rbx): a_purge_whose_token_is_refused_waits_for_a_new_one_instead_of_reconnecting_at_once (red c746d85: timed out waiting for the ask and the ask again, because the loop used all 40 refusals at once; green at caa7e7a with the 5 other cloud_host_replica tests and 23 cloud_conversation tests; clippy on cmux-tui-cloud-conversations clean).

Review 1 (subagent, correctness first) and resolution

3 P1, 4 P2, 2 P3. All fixed in 51f7f7b, e736e08 and 8609ff5 (renewal asks deduped and re-asked, purge mark keyed by user and daemon pid, lease order serialized, help text that names what leaves the Mac).

Review 2 (subagent, correctness first) and resolution

  • P1-A: a pending purge whose token the cloud refused (4401) reconnected with the dead token in a busy loop. Fixed in caa7e7a: a refused purge waits for a new lease and asks at the reask pace; only a replaced socket (4000) retries the purge, after a backoff. Red test c746d85.
  • P2-A: the refused wait asked again after a renewal or a clear woke it. Fixed: it asks only when nothing changed.
  • P2-B: a cloud that refuses every new token caused a loop of token mints. Fixed: asks of one reason are paced across leases.
  • P3: the app recorded a purge as asked when its lease returned early (no Cloud origin). Fixed.
  • P3 (accepted, known): another account's purge mark is replaced when a different user signs in on the same Mac and turns the setting on. The other account's copy then stays until that owner runs host.replica.purge or removes the host.

Review 3 (subagent, correctness first, on the review 2 fixes) and resolution

  • No P1. P1-A, P2-A, P2-B and the Swift P3 fixes were confirmed correct.
  • P2: one ask slot for both reasons let an expiring ask and an unauthenticated ask unlock each other, so a fast Mac clock and a refusing cloud minted a token per lease. Fixed in 075c20c: one slot per reason. Red test fc69ccf (expiring_and_refused_asks_keep_their_pace_across_leases: 2 expiring asks, expected 1).
  • P3: the purge retry read the status phase. Fixed: blocked stores why it stopped.
  • P3: the refused-purge test did not show recovery. Fixed: it now gives a new lease and sees replica.purge and off.
  • P3 (accepted, known): a renewal resets a pending purge, and the app sends set then clear {purge}; if the driver reaches the welcome in between, it publishes one snapshot and then purges it. The window is one local round trip. The opposite design (keep the purge on renewal) would purge when the owner turns the setting on again.
  • P3 (accepted): the first unauthenticated ask for a new lease can wait up to 30 s when an older lease was refused within 30 s.

Known and deliberate

  • Each publish event carries the whole projected tree in effects.state (small: about 12 fields per workspace). Deltas are not worth the complexity at this size.
  • An owner purge while the Mac is online drops the copy and closes the replica socket; the daemon stays off until the app clears the replica (the setting turned off and on, or a new sign-in).

Not covered

Size cap refactor (e57edd5)

safe-push refused the landing: CmuxNextApp grew past its source-line cap. The fix moves code out of the App target without a behavior change: CloudHostReplicaFollower moves to CmuxNextPhoneAccess (public, with injected install-token and invalidate closures), the link device name moves to MacName.linkDeviceName(), the JWT team claim moves to HomeCloudLease.team(ofJWT:), and pause/resume returns to CloudService.swift. App source: 83242 lines, base 83246.

Review 4 (subagent, correctness first, on e57edd5) and resolution

  • No P1, no P2. Access control, package dependencies, isolation and Sendable captures, the callers and the return of a Void call were checked.
  • P3 (accepted): HomeCloudLease.team(ofJWT:) keeps empty JWT segments, as expiry and subject do. Only malformed tokens give a different result; the team claim is never a credential.
  • P3 (follow-up in HQ): plans/cmux-next/object-sync/DESIGN.md still names the old follower path.

Landing

Landed as afe14bb on feat-cmux-next (safe-push merge train).

  • Full gate green on b9ea556: 34 steps. The CmuxNext lane retried CloudBringBackTests, CmuxNextPaletteTests and RapidWorkspaceCreateTests once (failed in the first run, passed in the retry).
  • Merged-head gate green on afe14bb: 6 steps (chain check of the full receipt, defaults check, release compile, PhoneAccess/Settings/Daemon/CloudBringBack/CloudMove suites, web bundles, rbx fmt/tests/clippy/workspace build). BranchStateResourcesTests ("the pinned tab") failed in the first run and passed in the retry.
  • safe-push warnings, red on the base too: main-actor work ratchet, CI target graph, text fields draw no focus ring.

none (feat-cmux-next lane; the setting is opt-in until #13742 decides)

🤖 Generated with Claude Code

lawrencecchen and others added 20 commits October 19, 2026 07:25
The publisher asks the app for a new install token before the lease ends
(cloud-host-replica-needed, expiring), a renewal keeps the socket, a 4401 at
the old token's expiry reconnects with the renewed token, and without one it
asks again (unauthenticated). Red on the earlier publisher (rbx): the
expiring ask never comes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d renews in place

cloud-host-replica-needed (expiring two minutes before expires_at, or
unauthenticated on 401/4401) goes to the verified app connection only. A
lease for the same user, install and origin swaps the token without a
reconnect; a 4401 at the old token's expiry reconnects at once with the
renewed token. Spec (commands.md, events.md, sdk-schema, inventory) and the
generated bindings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eader needs

Screens, tab and screen titles, the app and the kind stay on the Mac: a
title can name a folder or a command. The cloud copy keeps id, names and
their revision, group, color, icon, pinned, unread and active, plus the
registry generation that workspace.rename checks. The behavior test checks
the published keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne.cloudReplica)

The app leases this Mac's install token to the daemon's host replica
publisher (cloud-host-replica-v1) while Settings > Accounts > Phone Access >
phone.cloudReplica is on, the account is signed in and Cloud is allowed.
Turning it off purges the cloud copy; sign-out drops the lease;
cloud-host-replica-needed mints a new token. Opt-in until #13742 decides
the default. Agents may not turn it on (privacy policy).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st is off

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

TeamDO checks the host's owner (no agents, not an orphaned host), audits
the purge and queues a non-tombstone drop: HostDO forgets the stored streams
and closes the replica sockets. The host keeps its id and publishes again
while the Mac's own switch is on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated backend/catalog, the TS cloud client and the Rust cloud-wire
ops (bun catalog:export, catalog:client, catalog:rust on Freestyle).
DESIGN.md records the owner's purge and the Swift lease caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd refused to agents

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated on the fleet (run-swift-testing-suites.sh export-settings at
993ea2f); defaults_guard.py snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… lease comes

The reask option exists but nothing uses it yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…piring asks, never reuses a generation

Review P1-3, P2-4, P2-7: a refused token is asked for again every reask
(30 s) until a new lease comes; an expiring ask waits reask after the last
one of any lease (a Mac clock ahead of the cloud's no longer loops); lease
generations only go up, and a clear forgets the host so another user's
lease enrolls again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… confirms it

Review P1-1, P1-2, P3-8: the off switch leases and purges again after
every ask and never cancels a purge with a plain clear; the mark names the
account and is removed only when the daemon that was asked shows off; a
daemon restart on the same connection is a new want; Want is nonisolated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t matches the daemon

Review P2-5, P2-6, P3-9: the help says a workspace name can be a
folder's name (21 locales); spec/events.md names the re-ask and pacing;
team-host-replica-drop.ts says an online daemon stays off until the app
clears; DESIGN.md records the purge mark and the renewal cost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Regenerated on the fleet (export-settings at 8609ff5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A purge whose token the cloud refuses (4401) must wait for a new lease and
ask at the reask pace, not reconnect with the dead token at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second review of the host replica slice:
- P1: a pending purge whose token the cloud refused (4401) reconnected with
  the dead token in a loop. It now waits for a new lease like any refused
  lease; only a replaced socket (4000) retries the purge, after a backoff.
- P2: the refused wait asked again after a new lease or a clear woke it.
  It asks only when nothing changed.
- P2: asks of one reason are paced across leases, so a cloud that refuses
  every new token does not cause a loop of token mints.
- P3: the app records a purge as asked only when its lease went.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Third review: one ask slot for both reasons lets an expiring ask and a
refused ask unlock each other, so a fast clock and a refusing cloud mint a
token per lease without a pace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Third review of the host replica slice:
- P2: one ask slot let expiring and unauthenticated asks unlock each other,
  so a fast clock and a refusing cloud minted a token per lease. Each reason
  now has its own slot.
- P3: blocked stores why it stopped, so the purge retry no longer reads the
  status phase.
- P3: the refused-purge test now gives the new lease and sees the purge
  finish.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AppServices, CloudService and DaemonEndpoint no longer grow past their base
size: the settings follow stays on one line, pause and resume move to
CloudService+PauseResume.swift, and the capability shares the cloud
conversations doc. The replica replies and client move to
CloudHostReplicaClient.swift (3 types per file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lawrencecchen
lawrencecchen temporarily deployed to cmux-tui-merge-tree October 19, 2026 16:39 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Receipt for 3c080bf (this PR's head; a new push replaces it)

  • Visual review: HQ feed card · gallery before/after runs
  • Checks: this head
  • 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=pr22085 -f ref=cmux-next-gate-1d91f6081e4a -f platform=macos -f backend_mode=local

@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

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

Not verified: macOS jobs did not run: compile and app tests were skipped on 3c080bf28a; ci-status is green without them.

CI passes on 3c080bf28a (run 39289690895 attempt 1).
macOS jobs did not run: compile and app tests were skipped: Fast static checks skipped (the macos job needs both to succeed). Re-run CI once they pass, before merging.

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.

@lawrencecchen
lawrencecchen temporarily deployed to cmux-next-development October 19, 2026 16:48 — with GitHub Actions Inactive
@teamleaderleo

Copy link
Copy Markdown
Collaborator

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

- regenerated cmux-tui/crates/cmux-app-host/generated/scopes.json

Autofix-Run: https://github.com/manaflow-ai/cmux/actions/runs/39287221064
@blacksmith-sh

blacksmith-sh Bot commented Oct 19, 2026

Copy link
Copy Markdown

Found 1 test failure on Blacksmith runners:

Failure

Test View Logs
test_next_pushes_share_one_build_slot_that_the_newest_push_takes (main.NightlyTrust
edRouting.test_next_pushes_share_one_build_slot_that_the_newest_push_takes)/
test_next_pushes_share_one_build_slot_that_the_newest_push_takes (main.NightlyTrust
edRouting.test_next_pushes_share_one_build_slot_that_the_newest_push_takes)
View Logs

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

@github-actions

Copy link
Copy Markdown
Contributor

red: cmux-next CmuxNextApp size cap; do not merge until green; see lane-rules

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Landed as afe14bb on feat-cmux-next. The review record (reviews 1 to 4 and their resolution) and the gate receipts are in the PR body.

This branch was previously deployed

3 inactive deployments
artifacts — 3c080bf2 Deployed Oct 19, 2026 by manaflow-cmux-pr-autofix[bot]
cmux-next-development — 3c080bf2 Deployed Oct 19, 2026 by manaflow-cmux-pr-autofix[bot]
cmux-tui-merge-tree — 3c080bf2 Deployed Oct 19, 2026 by manaflow-cmux-pr-autofix[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants