Repository navigation
Reap persistent SSH daemon on workspace close - #8073
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe change adds scoped relay cleanup, authenticated persistent-daemon shutdown, daemon lock waiting, lease-aware reaping, and serialized workspace remote-session transitions. macOS teardown selects transport or persistent-slot cleanup, while remote daemon and lifecycle tests cover these flows. ChangesRelay teardown lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Workspace
participant RemoteSessionCoordinator
participant CmuxdRemote
participant PersistentDaemon
participant RelayState
Workspace->>RemoteSessionCoordinator: stop(cleanupScope)
RemoteSessionCoordinator->>CmuxdRemote: serve --persistent-stop --slot
CmuxdRemote->>PersistentDaemon: authenticated shutdown RPC
PersistentDaemon->>RelayState: wait for daemon lock and poll slot lease
RelayState-->>PersistentDaemon: lease absent
PersistentDaemon-->>CmuxdRemote: shutting_down
Possibly related PRs
🚥 Pre-merge checks | ✅ 23 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (23 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR adds final teardown for persistent SSH daemons. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (22): Last reviewed commit: "fix: retain failed transient cleanup own..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@daemon/remote/cmd/cmuxd-remote/persistent_lifecycle.go`:
- Around line 113-123: Bound the lock wait in waitForPersistentDaemonStop by
using nonblocking exclusive flock attempts with a deadline based on
persistentDaemonStopWaitTimeout. Retry briefly while the lock is unavailable,
return other flock errors immediately, and return a descriptive timeout error
when the deadline expires; unlock successfully acquired locks as before.
- Around line 20-40: Update existingPersistentDaemonPathsForSlot so a missing
storedSocketDir is treated like an already-removed daemon: when
verifyPrivateDaemonDirectory(storedSocketDir) returns os.ErrNotExist, return the
existing paths with found=false and no error. Continue propagating other
verification errors and preserve the current socket override behavior for
existing directories.
- Around line 42-55: Update stopPersistentDaemon’s missing-token handling so
that when paths.tokenFile is absent but paths.socket still exists, it removes
the stale socket and returns success, matching the existing stale-socket cleanup
behavior used after dial errors. Preserve the current clean success path when
both files are absent and continue returning other token-read errors.
- Around line 125-163: Update persistentDaemonSlotLeasePresent so transient
os.ErrNotExist failures from entry.Info and os.ReadFile are skipped with
continue, allowing the scan to inspect remaining relay entries; preserve
returning other errors and the existing matching-slot behavior.
- Around line 130-163: Update persistentDaemonSlotLeasePresent and its polling
flow to avoid rescanning and rereading ~/.cmux/relay on every one-second tick.
Add a cached or indexed slot-to-daemon lookup that is refreshed only when relay
contents change, while preserving the existing ownership, regular-file,
valid-port, and missing-directory behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c6ec4f9c-845c-4e3b-b62c-cf83d8d7d054
📒 Files selected for processing (11)
Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+RelayProvisioning.swiftPackages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+ReverseRelay.swiftPackages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator.swiftPackages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Values/RemoteRelayCleanupScope.swiftPackages/macOS/CmuxRemoteSession/Tests/CmuxRemoteSessionTests/RemoteRelaySlotTeardownTests.swiftdaemon/remote/README.mddaemon/remote/cmd/cmuxd-remote/main.godaemon/remote/cmd/cmuxd-remote/main_test.godaemon/remote/cmd/cmuxd-remote/persistent_lifecycle.godaemon/remote/cmd/cmuxd-remote/persistent_lifecycle_test.godocs/remote-daemon-spec.md
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/Workspace.swift`:
- Around line 5306-5314: Serialize replacement-controller startup with cleanup
in the session transition flow around previousController?.stop: when
preservesPersistentSlot is true, wait until the old controller’s transport
cleanup has completed before assigning or bootstrapping
remoteSessionCleanupController; otherwise preserve the existing cleanup
behavior. Ensure the replacement cannot rewrite the preserved relay port’s files
while the old cleanup is still pending.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 77cb684e-2be8-40dd-be5d-df7104fa3a11
📒 Files selected for processing (14)
Packages/macOS/CmuxCore/Sources/CmuxCore/Remote/WorkspaceRemoteConfiguration+SSHBatchCommands.swiftPackages/macOS/CmuxCore/Tests/CmuxCoreTests/WorkspaceRemoteConfigurationSSHBatchCommandsTests.swiftPackages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+RelayProvisioning.swiftPackages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+ReverseRelay.swiftPackages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator.swiftPackages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Values/RemoteRelayCleanupScope.swiftPackages/macOS/CmuxRemoteSession/Tests/CmuxRemoteSessionTests/RemoteRelaySlotTeardownTests.swiftSources/Workspace.swiftcmuxTests/RemoteDisconnectLifecycleTests.swiftdaemon/remote/README.mddaemon/remote/cmd/cmuxd-remote/main.godaemon/remote/cmd/cmuxd-remote/persistent_lifecycle.godaemon/remote/cmd/cmuxd-remote/persistent_lifecycle_test.godocs/remote-daemon-spec.md
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+RelayProvisioning.swift (1)
36-41: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winReject malformed persistent slots instead of treating them as nonpersistent.
A non-nil invalid slot normalizes to
nil, selecting the nonpersistent branch. If the slot file is already absent, final cleanup then skips daemon shutdown, removes its metadata/shell, reports success, and lets upstream discard retry ownership. Preserve the distinction between “not configured” and “configured but invalid,” returning a failing script for the latter.Proposed fix
let normalizedSlot = normalizedPersistentDaemonSlotForRemoteCleanup(persistentDaemonSlot) +if persistentDaemonSlot != nil, normalizedSlot == nil { + return "exit 64" +}Apply the same check in
remoteRelayTransportMetadataCleanupScript.As per coding guidelines, missing correctness-critical lifecycle state must fail closed rather than guess.
Also applies to: 80-82
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator`+RelayProvisioning.swift around lines 36 - 41, The normalization flow in the relay provisioning cleanup must distinguish an absent persistent daemon slot from a non-nil malformed slot. Update the logic around normalizedPersistentDaemonSlotForRemoteCleanup in the persistent-stop path and remoteRelayTransportMetadataCleanupScript so invalid configured slots return a failing script, while nil remains the nonpersistent case; do not proceed with cleanup or report success for malformed values.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@daemon/remote/cmd/cmuxd-remote/persistent_lifecycle.go`:
- Around line 193-208: The cleanup in
removePersistentDaemonRelayShellDirectoryIfUnleased must not perform an unlocked
Lstat-then-RemoveAll sequence. Coordinate it with runPersistentDaemonServer
using the same relay-port lock or lease token, and re-check the .slot while that
lock is held immediately before removing the .shell directory, preserving the
behavior that an active or newly created lease prevents cleanup.
---
Outside diff comments:
In
`@Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator`+RelayProvisioning.swift:
- Around line 36-41: The normalization flow in the relay provisioning cleanup
must distinguish an absent persistent daemon slot from a non-nil malformed slot.
Update the logic around normalizedPersistentDaemonSlotForRemoteCleanup in the
persistent-stop path and remoteRelayTransportMetadataCleanupScript so invalid
configured slots return a failing script, while nil remains the nonpersistent
case; do not proceed with cleanup or report success for malformed values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1f202c51-5ebd-452c-956b-ad62eb218b84
⛔ Files ignored due to path filters (1)
.github/swift-file-length-budget.tsvis excluded by!**/*.tsv
📒 Files selected for processing (14)
Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+Lifecycle.swiftPackages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+RelayProvisioning.swiftPackages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+ReverseRelay.swiftPackages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator.swiftPackages/macOS/CmuxRemoteSession/Tests/CmuxRemoteSessionTests/RemotePortScanGatingTests.swiftPackages/macOS/CmuxRemoteSession/Tests/CmuxRemoteSessionTests/RemoteRelaySlotTeardownTests.swiftSources/Workspace+RemoteSessionLifecycle.swiftSources/Workspace.swiftSources/WorkspaceRemoteSessionHostAdapter.swiftcmux.xcodeproj/project.pbxprojcmuxTests/RemoteDisconnectLifecycleTests.swiftcmuxTests/RemoteSessionCleanupLifecycleTests.swiftdaemon/remote/cmd/cmuxd-remote/persistent_lifecycle.godaemon/remote/cmd/cmuxd-remote/persistent_lifecycle_test.go
💤 Files with no reviewable changes (1)
- Packages/macOS/CmuxRemoteSession/Tests/CmuxRemoteSessionTests/RemotePortScanGatingTests.swift
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
…-8061-reap-persistent-server
…stent-server # Conflicts: # Packages/macOS/CmuxCanvasUI/Sources/CmuxCanvasUI/CanvasModel.swift
…-8061-reap-persistent-server
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
* test: reproduce leaked persistent SSH slot teardown * fix: reap persistent SSH daemon on workspace close * fix: preserve persistent slot state until shutdown * fix: harden persistent daemon lifecycle ownership * fix: retain failed remote cleanup ownership * chore: track merged event bus file budget * fix: serialize remote session cleanup handoff * fix: restore remote lifecycle test compatibility * test: avoid virtual sleep determinism false positive * fix: link terminal core tests with libc++ * test: make Ghostty runtime stubs weak fallbacks * fix: include relay catalog generator in Vercel builds * test: cover persistent cleanup ownership handoff * test: cover remote cleanup lifecycle gaps * test: keep remote lifecycle assertion target-visible * test: tighten remote cleanup lifecycle proof * fix: close persistent cleanup lifecycle gaps * fix: clamp extreme canvas reorder offsets * test: stabilize remote cleanup lifecycle teardown * test: reject malformed persistent cleanup slots * fix: reject malformed persistent cleanup slots * test: expose superseded remote cleanup race * fix: guard persistent cleanup ownership transitions * fix: block relay replacement after cleanup failure * test: retain failed transient cleanup owner * fix: retain failed transient cleanup owners (cherry picked from commit b250ada)
Summary
serve --persistent-stop --slot <slot>control that waits a bounded interval for daemon ownership release.daemon_path,.slot, and.shellacross reconnect, sleep, manual Disconnect, and same-identity controller replacementTest-first proof
The branch preserves red-before-green history:
5274a5a915adds the original failing clean-close regression before the persistent-slot teardown implementation.64ddc89ef5,7ed5ea64ef, anddcded14a93add/tighten lifecycle regressions, including A → B → A superseded-transition cleanup, before the follow-up production fix.5c5065d2b8adds the stale-transition guard and the narrowly scoped status-64 authenticated slot-stop fallback.f99e445120adds explicit final test teardown and increases only the test-harness observation timeout; no production behavior changes.The A → B → A regression was reproduced red on the pre-fix production code and green after
5c5065d2b8. On final merged HEAD64d4f37c39, the app-host lifecycle suite executed 8 tests with 8 passed, 0 failed, and 0 skipped; the.xcresultspecifically namessupersededDifferentIdentityTransitionCannotDestroyLatestPersistentSession()as passed.Latest-main integration
origin/mainthrough496a1c4bb9in64d4f37c39CanvasModel.reorderPanel; resolution takes main's canonical6a00674d0fimplementation verbatimorigin/mainis an ancestor of HEAD, and the final PR diff no longer contains a Canvas or web changeVerification on final HEAD
64d4f37c39cmux-unitapp-host test bundle built and linked withbuild-for-testingRemoteSessionCleanupLifecycleTests: 8/8 passed from a serialized app-host run; 0 failed/skippedCmuxRemoteSession: 89/89 passedCmuxCore: 51/51 passedCmuxCanvasUI: 45/45 passed, includingreorderPanelClampsExtremeOffsetsWithoutOverflowgo test ./...,go test -race ./...,go vet ./..., andgofmtcleanlinux/amd64scripts/check-pbxproj.shgit diff --checkfor both the worktree andorigin/main...HEAD./scripts/reload.sh --tag issue-8061-reap-persistent-serverNative process proof
A linux/amd64 daemon binary was exercised as real separate processes with an active interactive Bash PTY and realistic relay metadata:
.daemon_path,.slot, and.shellaliveThis proof passed natively on Ubuntu x86_64 and inside fresh Ubuntu 24.04.4 LTS x86_64 userspace, matching the reporter's platform.
Localization audit
No UI, Settings, menu, shortcut, schema, or localized message-catalog copy is changed by the final PR diff. The Swift/Go diff was checked for new user-facing surfaces; the only error-output change removes remote-provided detail and reuses the existing generic shutdown rejection. No localization entries are required.
Scope
This fixes the remote persistent-server leak on clean workspace close. Mac-side SSH/ControlMaster cleanup after an app crash remains explicitly out of scope.
Issue: #8061
Closes #8061