Skip to content

Fix SSH authentication cleanup and production env fixture - #9703

Closed
lawrencecchen wants to merge 134 commits into
mainfrom
fix-client-config-env-fixture
Closed

lawrencecchen wants to merge 134 commits into
mainfrom
fix-client-config-env-fixture

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add the production client-config fixture required by current main
  • make foreground SSH authentication cleanup own isolated process groups
  • validate PID, parent, process group, start time, and stopped state before force-killing
  • bound synchronous cleanup to 500 ms with a 2 s hard deadline
  • retain failed cleanup state for one bounded background reaper and a later recovery sweep

These changes must land together because current main requires the production environment fixture before the SSH regression suite can pass its speculative merge gate. This PR supersedes #9712 after merge.

Mechanism

Authentication runs in a cmux-owned process group. Cleanup freezes and snapshots the rooted tree, revalidates process identity, terminates children before parents, and keeps durable state when it cannot prove safe completion. A lock owner records PID plus start time so later attempts can distinguish a live reaper from stale state. Attach startup injects the SSH executable through one shared builder, and the app-host tests exercise the exact ssh -tt ... attach path.

This is a principled ownership fix. It models process lifetime and identity explicitly instead of extending timeouts or sending signals to unverified PIDs.

Validation

  • Exact head: d5a9a783b4d0940ce481c3cf9ecc76cb7d18716c
  • bun test tests/client-config-env.test.ts from web/: 17 passed
  • focused SSH cleanup policy suite: 35 passed
  • SSHPTYAttachRetryScriptBuilderTests: 6 passed
  • full CmuxFoundation suite on unchanged package sources: 184 passed
  • exact app-host SSHForegroundAuthenticationMarkerCleanupTests: 5 passed, including process-tree death, retry limit, marker removal, and signal-trap coverage
  • strict determinism check: passed with zero findings
  • exact no-cache cloud build: sgf5-0c12f0b27fc0, BUILD_OK
  • CodeRabbit: success, zero unresolved review threads

Merge state

  • The speculative merge gate will run on top of CI: serialize Swift Testing inside app-host shards #9717 because that PR stabilizes the shared app-host CI lane.
  • Canonical $autoreview reached this exact head but the required Codex account is quota-limited until August 12, 2026 PT. That review remains a hard merge blocker.

Safety

Cleanup fails closed on identity mismatch, never sends a bare unvalidated PID KILL, resumes processes if it cannot confirm a frozen snapshot, and preserves state when the hard deadline cannot prove safe cleanup.

Dictionary

  • Process group: An operating-system grouping that lets cmux contain and signal one authentication process tree without targeting unrelated processes.
  • Speculative merge: A temporary commit combining this PR with its intended base so CI tests the code that would actually land.
  • Exact-head build: A build whose source commit matches the PR head byte for byte.

Note

High Risk
Large generated shell surface around process signals, identity checks, and temp-dir locking on the SSH authentication path; regressions could leave processes stopped, leak auth state, or signal wrong PIDs.

Overview
Replaces ad hoc foreground SSH authentication teardown with owned process groups, durable temp state, and bounded recovery so cleanup cannot outlive the session or signal unverified PIDs.

Each auth attempt gets a CMUX_SSH_AUTH_GROUP_DIR created at startup/attach; the classifier publishes an isolated PTY anchor and group identity there. Cleanup snapshots and journals STOP/KILL in transactions (500 ms work budget, 2 s hard deadline), validates stable identity (group + kernel start time) before signals, and rolls back STOP journals when it cannot prove every target is frozen. Failed or incomplete cleanup launches a background reaper and enqueues the directory for a per-user recovery sweep; SSH startup always schedules one recovery pass so stale groups from prior sessions are reclaimed.

CLI startup and PTY attach retry scripts now wire cmux_ssh_remove_auth_group_dir into session end, signal handlers, and post-auth wait paths, and install termination helpers on every startup (not only when a one-time auth command exists). classifyingTransientFailure runs auth inside the owned group when a directory is present.

Also fixes the reusable shell startup wrapper to decode base64 via a cmux_payload variable instead of repeating the encoded literal.

Reviewed by Cursor Bugbot for commit d6632e1. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

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

@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR replaces recursive SSH process cleanup with identity-validated, snapshot-based group termination. It adds owned-group lifecycle handling, bounded recovery, temporary state directories, startup-script integration, and tests for cleanup, signals, deadlines, and recovery.

Changes

SSH authentication group cleanup

Layer / File(s) Summary
Owned-group snapshot and termination engine
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SSHForegroundAuthenticationRetryPolicy+OwnedGroups.swift
Adds bounded process snapshots, descendant expansion, exclusive-group detection, identity validation, freezing, reaping, and deepest-first termination.
Foreground authentication lifecycle and recovery
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SSHForegroundAuthenticationRetryPolicy.swift
Adds group-state removal, owned-group publication, signal and cancellation handling, cleanup handoff, recovery markers, and temporary capture-directory cleanup.
Attach and startup cleanup wiring
CLI/CMUXCLI+SSHStartupScripts.swift, Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SSHPTYAttachRetryScriptBuilder.swift, Sources/SSHPTYAttachStartupCommandBuilder.swift
Creates and exports authentication-group directories, invokes cleanup and reapers, removes state files, and clears directory environment state.
Process cleanup and script lifecycle validation
Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/SSHForegroundAuthenticationRetryPolicyTests.swift, Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/SSHPTYAttachRetryScriptBuilderTests.swift, cmuxTests/SSHForegroundAuthenticationMarkerCleanupTests.swift
Covers cleanup failures, deadlines, stale identities, ordering, signals, cancellation, reaper recovery, shell variants, diagnostics, and attach-script cleanup.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SSHAttachScript
  participant ForegroundAuthentication
  participant OwnedGroupState
  participant ProcessSnapshot
  SSHAttachScript->>OwnedGroupState: Create and export authentication-group directory
  SSHAttachScript->>ForegroundAuthentication: Start foreground authentication
  ForegroundAuthentication->>OwnedGroupState: Publish owned process-group identity
  OwnedGroupState->>ProcessSnapshot: Snapshot and validate process identities
  ProcessSnapshot-->>OwnedGroupState: Return owned descendants and groups
  OwnedGroupState->>ForegroundAuthentication: Freeze, reap, or terminate validated processes
  SSHAttachScript->>OwnedGroupState: Remove state files and group directory
Loading

Possibly related PRs

  • manaflow-ai/cmux#9083: Extends the same SSH foreground-authentication retry and lifecycle cleanup paths.
  • manaflow-ai/cmux#9710: Covers related deadline-based snapshot, identity validation, and deepest-first descendant cleanup.
  • manaflow-ai/cmux#9712: Covers related SSH authentication process-group snapshot and cleanup changes.

Suggested reviewers: austinywang


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (4 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error Production Swift now emits a polling loop with /bin/sleep 0.01 and a blocking wait for the reaper PID; both are new synchronization primitives. Replace owner-file polling and blocking reaper wait with an explicit signal or completion handshake that carries cancellation and bounded failure state.
Cmux No Hacky Sleeps ❌ Error Production-generated SSH shell adds a 100-iteration /bin/sleep 0.01 polling loop while waiting for reaper.lock/owner; this hides a shared-state handoff race. Replace the fixed polling loop with an explicit cancellation-aware handoff or process/file event that establishes owner readiness before the reaper proceeds.
Cmux Algorithmic Complexity ❌ Error New process cleanup sorts scalable process records at lines 99, 139, 165, and 195, with up to four snapshot/freeze retries; no explicit process-count bound or benchmark supports this O(n log n) path. Replace sort-based ordering with a linear-time depth/bucket plan, or add an explicit process-count threshold and benchmark evidence for the 1000-process case.
Cmux Architecture Rethink ❌ Error Production Swift builders add sleep/poll loops and a TMPDIR reaper.lock/reaper.failed protocol (policy lines 184–315, 375–420), making cleanup timing- and side-channel-dependent. Make the authentication wrapper the single owner of cleanup state and use one explicit completion/termination handoff. First remove marker scanning and file-lock polling, then prove bounded cleanup without sleeps.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed The PR adds only pure shell-builder methods and immutable arrays to existing Sendable structs; it adds no MainActor-sensitive models, service protocols, shared Sendable references, or UI-bound back...
Cmux Browser Automation Off-Main ✅ Passed The PR changes only SSH authentication and PTY startup files; the scoped browser automation sources and tests are unchanged relative to origin/main.
Cmux Expensive Synchronous Load ✅ Passed PR diff adds no RestorableAgentSessionIndex, SharedLiveAgentIndex, agent-history file, JSON, or synchronous file loads; production changes only emit bounded SSH process-group cleanup shell code.
Cmux Cache Substitution Correctness ✅ Passed The PR adds no cache/history/undo persistence path; process snapshots come from fresh ps reads and use deadline plus PID/PPID/PGID/start-time revalidation before KILL.
Cmux Swift Concurrency ✅ Passed The cumulative Swift diff adds no DispatchQueue/DispatchGroup, Task, Combine, async/await, or completion-handler APIs; background shell processes and Process/Thread test synchronization are allowed...
Cmux Swift @Concurrent ✅ Passed The full origin/main..HEAD Swift diff adds no async/await, nonisolated, @concurrent, or @MainActor code; changed functions synchronously build shell strings.
Cmux Swift Package Boundaries ✅ Passed SSH authentication cleanup logic is in the independently testable CmuxFoundation target via SSHForegroundAuthenticationRetryPolicy; app and CLI changes only compose lifecycle shell glue.
Cmux Swiftpm Lockfiles ✅ Passed The PR diff from origin/main changes only SSH Swift sources and tests; it contains no Package.swift, Package.resolved, .gitignore, workflow, or cmux.xcodeproj changes.
Cmux Swift Logging ✅ Passed The production diff adds no prohibited Swift logging; new printf writes store SSH cleanup protocol state, and the existing /usr/bin/logger diagnostic is unchanged.
Cmux User-Facing Error Privacy ✅ Passed The only new diagnostic logs deferred SSH cleanup to internal logger; its output is redirected, and no new user-facing text exposes vendor, provider, environment, credential, or payload details.
Cmux Full Internationalization ✅ Passed The diff changes only Swift SSH cleanup/script builders and tests; added text is shell operations, protocol/config tokens, or comments, with no user-facing copy or localization surface changed.
Cmux Swiftui State Layout ✅ Passed The merge-base diff changes only SSH/Foundation builders and tests; added-line and changed-file searches found no SwiftUI, ObservableObject, @Published, @Observable, GeometryReader, or lazy/list st...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The HEAD^→HEAD patch changes only SSH authentication shell/script builders and tests; it adds no NSWindow, NSPanel, NSWindowController, SwiftUI Window, or close-routing code.
Cmux Source Artifacts ✅ Passed The PR changes eight intentional Swift source and test files; the diff adds no binary files, logs, caches, temp directories, build output, or artifact-like paths.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No added DEBUG/test-build guards or test/debug-named members exist in changed production Swift; new shell builders have production callers in CLI and attach builders.
Cmux No Ambient Global State ✅ Passed Cumulative diff adds instance methods on SSHForegroundAuthenticationRetryPolicy and an extension member, plus static let constants; it adds no file-scope mutable state, static namespace, or singleton.
Title check ✅ Passed The title clearly identifies the SSH authentication cleanup and production environment fixture changes.
Description check ✅ Passed The description provides a detailed summary, rationale, risks, and validation results, but omits several template sections.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-client-config-env-fixture

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.

@lawrencecchen lawrencecchen changed the title Fix client config production test fixture Restore client config and SSH cleanup checks Aug 6, 2026
@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (2)
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SSHForegroundAuthenticationRetryPolicy.swift (2)

146-210: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Require a confirmed frozen snapshot before killing members.

If all eight calls to cmux_ssh_stop_auth_tree_members return nonzero, Line 202 still creates a kill order from a snapshot that contains running members. A running parent can fork after that snapshot. Its new child is absent from the kill list and can survive cleanup.

Only build cmux_ssh_auth_tree_kill_order after a snapshot confirms that every rooted member is stopped. If freeze confirmation fails, use a safe failure path instead of killing an unconfirmed list.

🤖 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/CmuxFoundation/Sources/CmuxFoundation/SSHForegroundAuthenticationRetryPolicy.swift`
around lines 146 - 210, Require successful freeze confirmation before
constructing or using cmux_ssh_auth_tree_kill_order: after the retry loop,
proceed only when cmux_ssh_stop_auth_tree_members has confirmed every rooted
member is stopped. If all eight attempts fail, resume
cmux_ssh_auth_tree_frozen_members and exit through the safe failure path instead
of killing the unconfirmed snapshot.

174-185: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Build the descendant tree without repeated full-process scans.

The while (cmux_discovered) loop scans every process in the global ps snapshot for every tree depth. Its cost is O(P × D) per freeze attempt, where P is all host processes and D is tree depth. The outer retry loop can run eight times.

Build a children[parentPID] index during the input pass. Then traverse only rooted descendants with a queue. This makes discovery O(P + D) before the required descendant sort.

As per coding guidelines, production code must avoid nested full-collection scans over scalable collections.

🤖 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/CmuxFoundation/Sources/CmuxFoundation/SSHForegroundAuthenticationRetryPolicy.swift`
around lines 174 - 185, Replace the repeated full-process discovery loop around
cmux_discovered with a children-by-parent index built during the initial
cmux_process input pass, then traverse only descendants rooted at the target
process using a queue. Preserve the existing depth and zombie filtering
behavior, and retain the required descendant sort after traversal; update the
surrounding symbols cmux_parent, cmux_depth, and cmux_process without scanning
the global process collection once per depth.

Sources: Coding guidelines, Path instructions

🤖 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
`@Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SSHForegroundAuthenticationRetryPolicy.swift`:
- Around line 249-259: The cleanup logic in both descendant traversals must keep
the authentication root stopped until the force scan completes. In
SSHForegroundAuthenticationRetryPolicy.swift lines 249-259 and 282-289, update
the cleanup-expired handling around cmux_ssh_terminate_auth_process and
cmux_ssh_auth_cleanup_has_time so stopped-parent propagation is preserved,
including when the parent is the authentication root; apply the same change at
both sites.

---

Outside diff comments:
In
`@Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SSHForegroundAuthenticationRetryPolicy.swift`:
- Around line 146-210: Require successful freeze confirmation before
constructing or using cmux_ssh_auth_tree_kill_order: after the retry loop,
proceed only when cmux_ssh_stop_auth_tree_members has confirmed every rooted
member is stopped. If all eight attempts fail, resume
cmux_ssh_auth_tree_frozen_members and exit through the safe failure path instead
of killing the unconfirmed snapshot.
- Around line 174-185: Replace the repeated full-process discovery loop around
cmux_discovered with a children-by-parent index built during the initial
cmux_process input pass, then traverse only descendants rooted at the target
process using a queue. Preserve the existing depth and zombie filtering
behavior, and retain the required descendant sort after traversal; update the
surrounding symbols cmux_parent, cmux_depth, and cmux_process without scanning
the global process collection once per depth.
🪄 Autofix

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 Plus

Run ID: f9667b43-0657-4f9e-8bc8-4f8a3babe1f6

📥 Commits

Reviewing files that changed from the base of the PR and between e6f2439 and 8e8d008.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SSHForegroundAuthenticationRetryPolicy.swift
  • Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/SSHForegroundAuthenticationRetryPolicyTests.swift

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d6632e1. Configure here.

fi
cmux_ssh_auth_recovery_unlock
fi
if [ "$cmux_ssh_auth_recovery_sweep_ready" != 1 ]; then exit 0; fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweep worker flock wait can time out

Medium Severity

The coalesced recovery worker waits for owner publication by calling cmux_ssh_auth_recovery_lock while the parent still holds that same flock. That helper hard-caps the wait at one second, so a slow post-fork owner publish makes the child exit before work starts; the parent then sees a dead owner and aborts the schedule. Failed auth-group recovery is skipped until a later startup or cleanup schedules again.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d6632e1. Configure here.

"$cmux_ssh_auth_stale_lock/generation" \
"$cmux_ssh_auth_stale_lock/generation.new" \
"$cmux_ssh_auth_stale_lock/pending" \
"$cmux_ssh_auth_stale_lock/pending.new" 2>/dev/null || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release helper omits pending markers

Low Severity

cmux_ssh_auth_release_reaper_lock_if_current still deletes only owner, publisher, and generation files, not the new pending markers. The sweep worker’s EXIT trap uses that helper, so an abnormal exit after another scheduler wrote pending leaves sweep.lock behind because rmdir fails.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d6632e1. Configure here.

@coderabbitai coderabbitai Bot mentioned this pull request Aug 11, 2026
4 of 6 tasks
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants