Skip to content

Forget and Devices Remove each take out both the SSH machine and its acpmux peer - #28799

Merged
cmux-bot[bot] merged 8 commits into
feat-cmux-nextfrom
lane/forget-remove-both
Oct 31, 2026
Merged

cmux-bot[bot] merged 8 commits into
feat-cmux-nextfrom
lane/forget-remove-both

Conversation

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Fixes break 8 of HQ #4877 (manaflow-ai/cmuxterm-hq#4877), the walkthrough of remote machines in NEXT. Leo asked there: "how is our setup flow anyways? like cmux-next remote and whatnot".

  1. Forget and Remove each undo only half.
    • remote.forget leaves the acpmux peer, which keeps running agents (design §6 says it should remove it).
    • Settings > Devices Remove runs cmux device remove, which leaves the sidebar's SSH machine.

Design

When you add a computer with Add Device, it lands in two lists: the sidebar's SSH machine, and the acpmux peer that enroll adds at the same ssh://[user@]host.

  • Forget Machine (remote.forget): SSHService.commitForget now also runs acp host rm for the peer at the machine's route. That write happens only when the Undo toast ends, so Undo needs no change: the peer is never removed while Undo is still possible. The peer is kept if another of the app's SSH sessions still uses the same route, for example a second named session on that host. When automation forgets a machine, the write happens right away, so the peer goes then too.
  • Settings > Devices Remove of a peer:NAME row: DeviceDirectory.remove first reads that peer's ssh route from acp host ls --json. If device remove succeeds, the SSH machines at that route are also forgotten, written at once (Remove for Good was already confirmed). A failed remove forgets nothing.
  • Matching is exact. The host must be the same (case is ignored), the user must be the same, and the SSH machine must have no ssh port, because a peer URL's port belongs to its daemon and never to ssh. A short name, a different user or a different port never matches. Peers that are not ssh:// are never touched.
  • Both changes are in Swift. removal.rs cannot see the app's SSH machines, and DeviceDirectory already runs the bundled cmux-tui. HostCandidate.sshTarget(peerURL:) is the existing ssh:// reader, pulled out as a static function so both places can use it.
  • plans/cmux-next/add-device-DESIGN.md §2.5 records the rule.

Risk

Medium-low. Removal is destructive, so it only acts on an exact route match. Peer removal runs only after a Forget has been written, and SSH-machine removal runs only after a confirmed Remove succeeded. The acp host rm call is best effort: if it fails, the peer stays, which is how things worked before this change. There are no new strings and no visible UI change.

Tests

Tests/CmuxNextAppTests/ForgetRemovesBothListsTests.swift:

  • route matching (case, user, port, short name, IPv6);
  • Forget removes the peer only once the Forget is written; an undone Forget removes nothing; only the matching peer is picked;
  • a second session at the same route keeps the peer;
  • acp host ls parsing (ssh peers only, daemon port dropped);
  • a Devices Remove of a peer forgets that route's SSH machine, while a failed remove or a non-peer row forgets nothing; removePeers removes only the matching peer.

No capture: there is no visible UI change (no new strings or controls). The only difference is that a machine now leaves both lists.

Agent session: agent session

🤖 Generated with Claude Code

…acpmux peer

HQ #4877 break 8: remote.forget left the acpmux peer, which kept running
agents, and Settings > Devices Remove left the sidebar's SSH machine.

- A written Forget (after its Undo toast ends) runs `acp host rm` for the
  peer at the machine's exact ssh route, unless another of the app's SSH
  sessions still uses that route. Undo needs nothing more: the peer is
  only removed when the Forget is written.
- Devices Remove of a peer reads its ssh route first, and once the remove
  succeeds forgets the SSH machines at that route.
- Routes match only exactly: host (any case), user, and no ssh port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4bbc87e99e6352cca67a
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

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

Review 103e799: 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=pr28799 -f ref=lane/forget-remove-both -f platform=macos -f backend_mode=local

web and size checks: green

Default changes

No defaults change at 255ca7595297.

Shipped in:

@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
…bind (a tracked default) is unchanged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4bbc87e99e6352cca67a
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (router-review gpt-6-astra high fast, independent Codex): SHIP at a3421a4

Range a06031f..a3421a4. No blocking findings.

Follow-ups (filed as manaflow-ai/cmuxterm-hq#5504, which the lane that opened this PR owns):

  • [P1] A reconnect to the same route while acp host ls/rm is running can still lose its peer. Recheck or serialize the route across the async removal.
  • [P2] A partial device remove (nonzero exit after the peer step succeeded) leaves the SSH machine behind.
  • From the review at f6dd8e8 (SHIP): Undo can come after an external peer remove; peer-removal failures are silent; peers that share a host and user but use different daemon ports are treated as one.

…arrived)

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.

…ep it while another Forget's Undo shows

Review at 4f15f4c (BLOCK): a reconnect during the peer CLI runs then lost its
session record to forgetSession; a second pending Forget on the route lost its peer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4bbc87e99e6352cca67a
@blacksmith-sh

blacksmith-sh Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Found 1 test failure on Blacksmith runners:

Failure

Test View Logs
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)/
test_every_listed_hook_is_one_glaeda_released_with_the_check (main.SigningHooksInSt
epWithGlaedaTests.test_every_listed_hook_is_one_glaeda_released_with_the_check)
View Logs

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

…ark is its own

Review at 43964a9 (BLOCK): a reconnect during host ls/rm still lost the peer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4bbc87e99e6352cca67a
@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 (router-review gpt-6-astra high fast, independent Codex): SHIP at 103e799

Range 4f4a957..103e799. This follows BLOCKs at 4f15f4c and 43964a9 (a reconnect racing the peer removal, stale Undo marks), both fixed here. Follow-ups are filed on manaflow-ai/cmuxterm-hq#5504:

SHIP at 103e799

Visual verdict: no images supplied (range review); the visual verdict must come separately

  1. [P2] [follow-up] Reconnect can still race with peer removal. tree/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Remote/SSHService.swift:73
    The predicate runs before HostCLI.json, which then awaits environment preparation and runs a separate process. A reconnect during that interval can restore the machine before host rm deletes its peer. Nothing cancels or compensates for that deletion. The added test reconnects before predicate evaluation, so it misses this interval. Coordinate reconnects with an outstanding removal and test this ordering.

  2. [P2] [follow-up] Forget leaves an open Devices page stale. tree/Packages/macOS/CmuxNext/Sources/CmuxNextApp/Remote/SSHService.swift:68
    The production removal closure deletes peers directly without refreshing services.devices. With Settings already open, its cached listing continues showing the removed peer until another refresh. The new DeviceDirectory.removePeers method refreshes the listing, but production never calls it. Refresh the directory after successful peer removal.

Static review completed; macOS tests could not run because this Linux environment lacks Swift.

Follow-ups:

  • Coordinate reconnects with in-flight peer removal and cover the race.
  • Refresh the Devices listing after Forget removes a peer.

router-review: follow-up: Coordinate reconnects with in-flight peer removal and cover the race.
router-review: follow-up: Refresh the Devices listing after Forget removes a peer.
router-review: SHIP at 103e799 (gpt-6-astra); 2 follow-up(s) to file as issues

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol high fast, router-review): SHIP at f338b2b

The head is a lander base merge (update-branch) of 103e799, taken to pick up base fixes (#29007 xcstrings, current guards). No blocking finding in the PR's own diff.

Follow-ups:

  1. [P1] SSHService forgetSavedHost fire-and-forget before peer removal: SSHService: forgetSavedHost is fire-and-forget before peer removal #29329
  2. [P1] SSHService route predicate and acp host rm not atomic: SSHService: route predicate check and acp host rm are not atomic #29330

@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 12:15 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent October 31, 2026 12:15 Inactive
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol high fast, router-review): SHIP at 78940de

The head is a cmux-bot base merge of f338b2b. No blocking finding in the PR's own diff.

Follow-ups:

  1. [P1] SSHService.swift:65-67 route check before awaited acp host rm is not atomic: SSHService: route predicate check and acp host rm are not atomic #29330
  2. [P1] SSHService.swift:333-345 forgetSavedHost fire-and-forget before peer removal: SSHService: forgetSavedHost is fire-and-forget before peer removal #29329
  3. [P2] DeviceDirectory.swift:97-100 device remove proceeds when acp host ls fails: 29352

@cmux-bot
cmux-bot Bot merged commit 4a46cfa into feat-cmux-next Oct 31, 2026
244 of 251 checks passed
@cmux-bot
cmux-bot Bot deleted the lane/forget-remove-both branch October 31, 2026 15:05
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol high fast, router-review): SHIP at 255ca75

This head is the lander's update-branch for stale guards. The full diff was reviewed.

  1. [P1] [follow-up] SSHService.swift:334: forgetSavedHost's CAS write is fire-and-forget before peer removal. Already filed as SSHService: forgetSavedHost is fire-and-forget before peer removal #29329.
  2. [P1] [follow-up] SSHService.swift:71: the route predicate check and acp host rm are not atomic. Already filed as SSHService: route predicate check and acp host rm are not atomic #29330.

Follow-ups: #29329, #29330

cmux-bot Bot pushed a commit that referenced this pull request Oct 31, 2026
… row key) (#29486)

The feat tip does not compile its tests (merge-batch compile job
adbfd4f046d9e75e9635af65 at 644491f):
- ForgetRemovesBothListsTests.swift:18: cannot find 'MainWorkScheduler'
  / 'MainFrameBudget' in scope. The file came with 4a46cfa (#28799)
  without `import CmuxNextWakeups`, where both types are public.
- SidebarStatusGroupingTests.swift:58: type 'SidebarRowKey' has no member
  'tab'. 46cf844 (#28563) added `if case .tab = row.key` after
  8a3af6b had deleted the sidebar's tab-row keys; with no tab rows, the
  line has nothing to skip, so it goes.
cmux-bot Bot pushed a commit that referenced this pull request Oct 31, 2026
…_lines) (#29478)

#28799 (4a46cfa) added 75 CmuxNextApp Swift lines (SSHService.swift,
AddDeviceServices+App.swift) with no exception entry; arch-ratchet's
app_lines row refuses it alone (--base 4a46cfa^ --head 4a46cfa: +75),
and the tip watcher's range 30806df..644491f is +75.

- SSHPeerForgets (new, CmuxNextServer/AddDevice, next to DeviceDirectory)
  owns the removal of a written Forget's acpmux peer: the default `acp host
  ls`/`acp host rm` through the app's cmux-tui, the marks of Forgets whose
  Undo still shows, the pending Forget record (Pending, was
  SSHService.PendingForget) and the exact-route match (reaches, sameRoute).
  CmuxNextServer now depends on CmuxNextRemote (ui -> service).
- SSHService keeps the calls: hide, unhide, commitForget (with its "Forget
  still holds and no other session reaches the route" check),
  forgetMachines(reachingPeer:) and the connect's mark clear.
- Reconnect's decision table moves to CmuxNextRemote as
  SSHConnectionMachine.Status.reconnectStep(daemonConnected:daemonFailed:)
  (it read only `daemon == .connected`), so the move takes out more App
  lines than #28799 added.

CmuxNextApp -79; the watcher's range is -4. No behavior change.
ForgetRemovesBothListsTests read the same cases through the moved code, now
also check that a written Forget takes the SSH machine out of the list as
well as its peer, and test SSHPeerForgets on its own.
RemoteConnectRecoveryTests read the moved reconnect step.
lawrencecchen added a commit that referenced this pull request Oct 31, 2026
…xtLayout (app_lines) (#29514)

The tip's arch-ratchet app_lines row is red over 30806df: three
commits add CmuxNextApp lines with no exception entry, each refused alone
by `arch-ratchet.py --base <c>^ --head <c>`:
- 0703de4 (#28049, Move Space to This Window / Merge All Windows): +99
- 088b1bc (#27919, a link from another app never reshapes Chief's Home): +16
- 7a12ed3 (#27825, terminals stay quiet while a handoff adopts their shell): +12
(the last two came in through the direct-push merge 8dd8bdc; #28799's
+75 is #29478's).

Their own code is wired into WindowManager, ExternalOpenController and
AppControl, so it cannot leave the App by itself. Instead, the App's pure
window membership model moves out whole: WindowRegistry (struct, Window,
Changes, every transition) and its repairedSelection extension go
unchanged to CmuxNextLayout/Windows with `public` access; its persistence
extension (WindowRegistry+Records) and the observable store stay in the
App. WindowRegistry used only CoreGraphics and Foundation (and Design's
FocusAfterClose, which CmuxNextLayout already depends on). App and test
files that name the type import CmuxNextLayout.

CmuxNextApp -426 against the tip; the watcher's range from 30806df is
app_lines -226 with this alone. No behavior change.
teamleaderleo added a commit that referenced this pull request Oct 31, 2026
Base moved the reconnect step to SSHConnectionMachine.Status.reconnectStep
(#28799); this lane's SSHReconnectStep goes, and .restart with retriesSpent
(HQ #4877 G4) joins base's API. SSHMachineSession.waitsForUser reads the
link's gate directly, not only once the mirrored status reads .failed (review
P1 follow-up at c11edce: the status reaches the session asynchronously).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-4bbc87e99e6352cca67a

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 255ca759 Deployed Oct 31, 2026 by teamleaderleo via wait for same-key tree publisher #90289
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