Skip to content

fix(windows): serialize worktree terminal teardown - #8284

Closed
bbingz wants to merge 52 commits into
stablyai:mainfrom
bbingz:fix/windows-terminal-daemon-worktree-removal
Closed

fix(windows): serialize worktree terminal teardown#8284
bbingz wants to merge 52 commits into
stablyai:mainfrom
bbingz:fix/windows-terminal-daemon-worktree-removal

Conversation

@bbingz

@bbingz bbingz commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • serialize worktree deletion behind a central PTY admission fence covering renderer and runtime spawn preparation through registration
  • preserve graceful generic terminal.stop, while worktree deletion uses awaited, verified teardown before Git worktree removal, recursive worktree deletion, or registry/metadata removal
  • fail closed when local, daemon, or SSH PTY teardown cannot be verified
  • deduplicate UI/runtime remove and forget owners so only one teardown and mutation can win
  • force-kill the full POSIX forkpty session during SSH teardown, with descendant-first Darwin ordering, so background job groups cannot survive with a deleted cwd
  • make the POSIX fail-closed regression platform-deterministic and make the latest-main Windows immediate-shutdown test await the ConPTY exit event

Root cause and invariant

The original removal path could begin graceful asynchronous PTY shutdown and then immediately enter provider and registry fallback sweeps. On Windows, overlapping ConPTY teardown against a shared daemon could terminate unrelated sessions.

Once removal owns a worktree, no new PTY for that worktree may enter or finish registration. Already-admitted spawns drain, every authoritative owned PTY is stopped and verified, and only then may destructive worktree mutation begin. Generic terminal.stop remains graceful and still permits shell EXIT traps. Local and daemon fallbacks are deduplicated and verified against an authoritative post-stop inventory; any unverified local, daemon, or SSH stop blocks deletion.

Configured symlink cleanup and the existing pre-removal archive hook can still run before PTY teardown. The safety claim begins before Git worktree removal, recursive worktree deletion, and registry/metadata removal; this PR does not claim that teardown precedes every filesystem operation.

Performance and resource bounds

  • 50 target PTYs across 21 daemon/degraded adapters: exactly 50 shutdowns and 50 targeted liveness probes, not 1,050 adapter scans
  • current SSH relay: targeted pty.hasPty checks and zero listProcesses inventories
  • legacy SSH relay: one capability miss and one deduplicated bounded listProcesses inventory across multiple PTYs
  • shutdown-owner verification cache is bounded to 4,096 one-shot entries and invalidated on spawn, attach, revive, reconcile, and dispose
  • identical UI/runtime removals join one operation; conflicting options or remove-versus-forget reject
  • verified stops run through an eight-worker bound; Linux uses pkill -s <sid>, while Darwin uses a targeted TTY inventory and descendant-first signals after root ownership verification

Screenshots

No visual change.

Testing

Final pushed HEAD: 8a745733d (author implementation plus deep-review hardening and the durable reliability gate)

  • pnpm lint on the final local HEAD
  • pnpm typecheck on the final local HEAD
  • pnpm test — 29,951 passed and 42 skipped on the final source using the Node 26 localStorage compatibility flag and a CI-equivalent clean Git-config environment
  • pnpm buildpnpm build:desktop passed on the final local HEAD; exact-head build:unpack is delegated to PR CI because it is the repository's packaging gate.
  • Added or updated high-quality regression coverage

Additional focused verification on the final local HEAD:

  • reviewed and retained maintainer hardening commit 960ba8c1: protocol v22, PID/TTY ownership revalidation, child-parent revalidation, all-session stop settlement, and authoritative fallback state retirement

  • 17 affected test files: 1,622/1,622 passed

  • relay Windows/POSIX teardown files: 91/91 passed

  • git diff --check passed

Prior live evidence retained by the implementation:

  • Windows 11 contributor harness: 100 sequential victim PTYs reaped while the daemon PID and unrelated witness survived
  • macOS Electron smoke: sequential victim removals preserved the daemon and unrelated two-PTY witness
  • Ubuntu 24.04 ARM64 SSH relay: sequential and batched removals killed foreground/background victim groups, preserved the relay daemon and unrelated witness, and failed closed while disconnected

AI Review Report

The review rechecked the current PR diff, removal ordering, admission fencing, daemon/local/SSH ownership, bounded liveness verification, Windows ConPTY semantics, POSIX session teardown, WSL-shaped providers, and duplicate remove/forget ownership. CodeRabbit correctly identified that the POSIX fail-closed test inherited the host platform; its earlier “addressed” marker was a false positive after a baseline merge. The test now explicitly selects Linux and restores process.platform. Merging the latest main exposed a second Windows test that did not emit ConPTY onExit; that regression was reproduced as a 30-second timeout and corrected to await the native exit event.

Cross-platform review covered macOS, Linux, Windows, WSL, and SSH. This PR does not change shortcuts, shortcut labels, or user-visible paths.

Security Audit

The review checked command construction, PID/TTY validation, process-selection scope, path ownership, IPC trust boundaries, cache bounds, and fail-closed behavior. POSIX process commands use bounded argument arrays rather than shell interpolation; Darwin validates the PTY name and verifies that the captured TTY still owns the root PID before signalling descendants. Missing tools, malformed output, timeouts, ambiguous ownership, and disconnected relays block destructive removal. No auth, secrets, dependencies, or external network behavior are added.

Notes

  • Fresh physical Windows/native WSL and paired mobile hardware remain live-environment gaps on the final HEAD; deterministic tests plus the retained Windows harness evidence cover the touched teardown behavior.
  • Daemon-crash session restoration/reporting is defense-in-depth outside this root-cause fix; this PR prevents the worktree-removal path from causing the cross-worktree daemon/session loss reported in the issue.
  • Current CodeRabbit review and GitHub PR checks should be evaluated against the final pushed HEAD above; earlier approval/review commits are stale evidence.

Closes #8275

Maintainer update (2026-07-14)

The branch was reset to the author-verified head 73dd9161, merged with latest main, and extended with a small audited fix set. An interim batch of maintainer review-hardening commits previously on this branch was withdrawn after an audit found it introduced regressions; those items move to a follow-up PR. Verification on the new head: pnpm typecheck and pnpm lint pass; 4,591 targeted tests across src/relay, src/main/daemon, src/main/providers, src/main/ipc, and src/main/runtime pass.

Maintainer deep-review update (2026-07-14)

A final correctness, cross-platform, SSH, cleanup, security, and performance audit found and fixed four additional failure-path issues before merge:

  • Windows relay graceful-close fallback retained no authoritative owner after its native exit event failed to arrive, so a later destructive retry could fail open. The disposed ConPTY owner now remains retryable until real native exit, repeated retries issue no second close, and relay disposal also avoids a duplicate ConPTY close.
  • POSIX child/session inventories silently filtered malformed PID tokens or accepted ambiguous columns. All PID, owner, TTY, child-parent, and final-exit evidence now requires canonical bounded output and fails closed.
  • POSIX final verification repeated per-PID signal-0 probes on every poll. It now performs at most two fast probes per PID, then uses 64-PID batched ps checks under the existing 500 ms deadline.
  • Local watcher shutdown and provider-only legacy-daemon handling now occur only after teardown is verifiable, so a failed removal leaves the worktree usable and watched.

Reliability contract

  • Invariant: terminal-session.worktree-removal-isolation — once removal owns a host/worktree, no PTY may enter or finish registration; every admitted target PTY must be fully verified stopped before destructive Git/filesystem/metadata mutation; ambiguity preserves the worktree and all unrelated sessions.
  • Failure source: [Bug]: Terminal daemon dies after batch worktree removal, terminating unrelated split-pane sessions #8275, including daemon loss and unrelated split-pane termination during rapid Windows worktree removal.
  • Oracle/gate: experimental gate terminal-session.worktree-removal-isolation in config/reliability-gates.jsonc; its exact 15-file command passed 966/966 tests in 11.56s.
  • Provider/platform coverage: deterministic local/current+legacy daemon/degraded daemon/current+legacy SSH/disconnected SSH coverage forces Linux, Darwin, and Windows semantics. A final-source macOS live PTY harness killed the victim root+child in 99 ms while an unrelated witness survived.
  • Performance budget: eight concurrent stops maximum; current SSH uses targeted liveness and legacy SSH shares one bounded inventory; POSIX tree size is capped at 1,024 with 64-PID batches, a shared 2.5s discovery deadline, and 500 ms final verification. No polling or scans were added to typing, output, focus, startup, or render paths.
  • Diagnostics: teardown logs retain target PTY ids and runtime/provider/registry stop counts without recording terminal output.
  • Residual gaps: exact-head physical Windows, native WSL, and live SSH runs remain pending; daemon-crash restore/reporting remains defense in depth outside this root-cause fix.

Final verification

  • full changed surface: 21 files, 1,883/1,883 tests passed
  • full repository suite: 2,832 files passed, 6 skipped; 29,951 tests passed, 42 skipped
  • pnpm lint passed (pre-existing warnings only)
  • pnpm typecheck passed for node, CLI, and web
  • relay bundles built for Linux x64/arm64, macOS x64/arm64, Windows x64/arm64, and WSL
  • git diff --check, max-lines ratchet, and reliability-gate validation passed

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

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 runtime coordinates worktree-scoped PTY admission and teardown across terminal spawning, splitting, and deletion. Teardown awaits verified PTY shutdown via stopAndWait, reports failed PTY IDs, and rejects cleanup if verification fails. Daemon routers and degraded providers track shutdown ownership for later liveness checks. SSH providers add targeted ID-only hasPty probes with cached legacy inventory fallback. Relay handlers and local/daemon providers implement POSIX session teardown via killPosixPtySession with descendant freezing, killing, and verification, plus Windows ConPTY waiting with timeout fallback. IPC handlers register spawning and removal operations with cross-surface deduplication and admission-held semantics. Tests cover admission gating, split admission, removal deduplication, cleanup ordering, verified stops with failure cases, SSH liveness capability fallback, POSIX full-session teardown, Windows exit waiting, daemon shutdown ownership tracking, and remote/local removal scenarios.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #8275 by fencing worktree teardown, verifying PTY shutdown before mutation, and protecting unrelated terminal sessions.
Out of Scope Changes check ✅ Passed The added modules and refactors all support PTY admission, teardown verification, or related tests; no clear unrelated code changes stand out.
Title check ✅ Passed The title is concise and accurately summarizes the main change: serializing worktree terminal teardown.
Description check ✅ Passed The description matches the repository template and includes the required summary, screenshots, testing, review, security, and notes sections.

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.

@AmethystLiang AmethystLiang self-assigned this Jul 11, 2026
@AmethystLiang
AmethystLiang self-requested a review July 11, 2026 16:47

@brennanb2025 brennanb2025 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed through a clean two-lane Codex gpt-5.6-sol high loop on exact pushed head 60e7cb3. Terminal lifecycle, SSH fail-closed behavior, bounded teardown performance, platform simulations, and live macOS isolation evidence are documented in the PR body.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/providers/ssh-pty-provider.ts (1)

252-258: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not mark graceful shutdowns as stopped.

When pty.hasPty is unavailable, this cache update makes hasPtyAsync() return false immediately after any successful shutdown request. The relay’s graceful path only sends SIGTERM and retains the managed PTY until it exits or its fallback fires, so teardown can be falsely “verified” while the PTY remains alive. Only mark it stopped for synchronous immediate shutdown; otherwise wait for pty.exit.

Proposed fix
     await this.mux.request('pty.shutdown', {
       id: this.toRelayPtyId(id),
       immediate: opts.immediate ?? false,
       keepHistory: opts.keepHistory ?? false
     })
-    this.ptyLiveness.markStopped(id)
+    if (opts.immediate) {
+      this.ptyLiveness.markStopped(id)
+    }
🧹 Nitpick comments (2)
src/main/daemon/shutdown-verification-owner-cache.ts (1)

4-4: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a dedicated unit test for this class.

No test file for ShutdownVerificationOwnerCache was included in this batch. Given this cache underpins the shutdown-fencing correctness across both DaemonPtyRouter and DegradedDaemonPtyProvider, a small dedicated test (verifying remember/take/eviction at the limit boundary, and that re-remembering an existing id doesn't grow size) would guard against regressions independent of the two consumer test suites.

Also applies to: 23-28

src/main/daemon/daemon-pty-router.ts (1)

235-261: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate reconciliation loop — reuse the new shared helper.

This reconcileOnStartup still hand-rolls the same alive/killed aggregation loop that degraded-daemon-pty-provider.ts now delegates to reconcileDegradedDaemonSessions (from the new degraded-daemon-session-reconciliation.ts). Since that helper is generic over DaemonPtyAdapter[], it can be reused here too, removing the duplicated loop and keeping the two shutdown-ownership invalidation paths from drifting apart in the future.

♻️ Proposed refactor
+import { reconcileDegradedDaemonSessions } from './degraded-daemon-session-reconciliation'
+
   async reconcileOnStartup(validWorktreeIds: Set<string>): Promise<{
     alive: string[]
     killed: string[]
   }> {
-    const alive: string[] = []
-    const killed: string[] = []
-    for (const adapter of this.allAdapters()) {
-      const result = await adapter.reconcileOnStartup(validWorktreeIds)
-      for (const id of result.alive) {
-        alive.push(id)
-      }
-      for (const id of result.killed) {
-        killed.push(id)
-      }
-      for (const id of result.alive) {
-        this.sessionAdapters.set(id, adapter)
-        this.shutdownVerificationAdapters.delete(id)
-      }
-      for (const id of result.killed) {
-        this.sessionAdapters.delete(id)
-        this.shutdownVerificationAdapters.delete(id)
-      }
-    }
-    return { alive, killed }
+    return reconcileDegradedDaemonSessions(
+      this.allAdapters(),
+      validWorktreeIds,
+      (id, adapter) => {
+        this.sessionAdapters.set(id, adapter)
+        this.shutdownVerificationAdapters.delete(id)
+      },
+      (id) => {
+        this.sessionAdapters.delete(id)
+        this.shutdownVerificationAdapters.delete(id)
+      }
+    )
   }

Note: the helper's name (reconcileDegradedDaemonSessions) is specific to the degraded-provider context; consider renaming it to something adapter-agnostic (e.g. reconcileDaemonAdapterSessions) if reused here.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 87bea1f9-9455-4cf6-a868-7f74050edc97

📥 Commits

Reviewing files that changed from the base of the PR and between 9a5a078 and 60e7cb3.

📒 Files selected for processing (25)
  • src/main/daemon/daemon-pty-router.test.ts
  • src/main/daemon/daemon-pty-router.ts
  • src/main/daemon/degraded-daemon-pty-provider.test.ts
  • src/main/daemon/degraded-daemon-pty-provider.ts
  • src/main/daemon/degraded-daemon-session-reconciliation.ts
  • src/main/daemon/provider-replay-subscription.ts
  • src/main/daemon/shutdown-verification-owner-cache.ts
  • src/main/ipc/pty-startup-barrier-ordering.test.ts
  • src/main/ipc/pty.test.ts
  • src/main/ipc/pty.ts
  • src/main/ipc/worktrees.test.ts
  • src/main/ipc/worktrees.ts
  • src/main/providers/ssh-pty-liveness.ts
  • src/main/providers/ssh-pty-provider.test.ts
  • src/main/providers/ssh-pty-provider.ts
  • src/main/providers/types.ts
  • src/main/runtime/orca-runtime.test.ts
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/rpc/terminal-stop.test.ts
  • src/main/runtime/worktree-pty-admission.test.ts
  • src/main/runtime/worktree-pty-admission.ts
  • src/main/runtime/worktree-teardown.test.ts
  • src/main/runtime/worktree-teardown.ts
  • src/relay/pty-handler.test.ts
  • src/relay/pty-handler.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1075a4af-26ae-4d76-900f-2ce4755985a3

📥 Commits

Reviewing files that changed from the base of the PR and between 1599f5a and d412681.

📒 Files selected for processing (10)
  • src/main/ipc/worktrees.test.ts
  • src/main/ipc/worktrees.ts
  • src/main/runtime/orca-runtime.test.ts
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/worktree-teardown.test.ts
  • src/main/runtime/worktree-teardown.ts
  • src/relay/pty-handler.test.ts
  • src/relay/pty-handler.ts
  • src/relay/pty-session-kill.test.ts
  • src/relay/pty-session-kill.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/relay/pty-session-kill.test.ts
  • src/relay/pty-handler.ts
  • src/main/runtime/worktree-teardown.test.ts
  • src/main/ipc/worktrees.ts
  • src/main/runtime/orca-runtime.test.ts
  • src/main/runtime/orca-runtime.ts

Comment thread src/relay/pty-handler.test.ts Outdated
@brennanb2025

Copy link
Copy Markdown
Contributor

Taking a look!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/relay/pty-session-kill.ts (1)

222-244: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Poll unresolved PIDs until the deadline verifyProcessesStopped returns false on the first live ps snapshot, so killPosixPtySession can fail on normal SIGKILL reaping races and surface as a teardown error. Retry the remaining PIDs until PTY_SESSION_VERIFY_TIMEOUT_MS expires.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ab147757-f7c9-489a-be88-9764511a4143

📥 Commits

Reviewing files that changed from the base of the PR and between 7666c0e and 244c4a7.

📒 Files selected for processing (9)
  • src/main/daemon/daemon-foreground-confirmation-protocol.test.ts
  • src/main/daemon/types.ts
  • src/main/ipc/pty.ts
  • src/main/runtime/pty-stop-concurrency.test.ts
  • src/main/runtime/pty-stop-concurrency.ts
  • src/main/runtime/worktree-teardown.test.ts
  • src/main/runtime/worktree-teardown.ts
  • src/relay/pty-session-kill.test.ts
  • src/relay/pty-session-kill.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/runtime/pty-stop-concurrency.ts
  • src/main/runtime/worktree-teardown.ts
  • src/main/runtime/worktree-teardown.test.ts
  • src/main/ipc/pty.ts

@bbingz

bbingz commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Final exact-head CI is green for authoritative head 244c4a7bcbdfa08dfe59c247d2219ad5013bc06e against base 0b65d725c98d6552f08fb512f4026b350b7fbf0b:

The verify job includes lint, typecheck, full tests, unpacked app build, and packaged CLI smoke. Local/fork/upstream PR heads are identical; the PR is MERGEABLE and APPROVED. The temporary fork validation PR is closed and its base branch removed.

@bbingz

bbingz commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@brennanb2025
brennanb2025 self-requested a review July 14, 2026 19:15
@brennanb2025
brennanb2025 force-pushed the fix/windows-terminal-daemon-worktree-removal branch from 958a3a2 to 0bf7c87 Compare July 14, 2026 20:22
@brennanb2025

Copy link
Copy Markdown
Contributor

Head change notice (maintainer): this branch was force-updated from 958a3a288 to 0bf7c87f7. What happened and why:

After 73dd9161 (the author's verified final head), a batch of 12 maintainer review-hardening commits had been pushed to this branch. A commit-by-commit audit found that wave introduced regressions whose fixes existed only in later, unpushed work — including graceful tab-close throwing when session capture fails, unclosable tabs on WSL/legacy SSH hosts, and a broken legacy-provider reconnect arity. Rather than pulling in the much larger fix chain, the branch was reset to 73dd9161, merged with latest main, and extended with only the audited, self-contained fixes:

  • a3bba3198 — make relay teardown tests platform-explicit (deterministic off-Linux)
  • 2d1b78188 — centralize teardown budgets; relay Windows exit budget 3s→8s to match node-pty ConPTY behavior
  • 13efa6a67 — fail closed when a legacy (protocol ≤21) daemon session cannot prove full teardown, instead of vacuously passing verification during the post-upgrade window
  • c4ff460d5 — reset the ConPTY kill-issued flag when kill() throws so teardown can retry instead of failing forever
  • 20f470def + c3073823f — dedupe ConPTY close between graceful shutdown, its SIGKILL fallback, and overlapping worktree-deletion teardown
  • 04b3782dc — keep Forget Locally best-effort when the SSH host is unreachable
  • 0bf7c87f7 — align a test with registerPty's new isWsl parameter from main

Verification on 0bf7c87f7: typecheck and lint pass; 4,591 targeted tests across relay/daemon/providers/ipc/runtime pass; the merge resolution in createTerminal was mechanically checked to preserve both the admission fence and all newer main edits. Deeper hardening items from the audit (graceful-close-then-delete visibility, forkpty capture TOCTOU, reparented-descendant escape, spawn-admission TOCTOU, WSL verification semantics) are deliberately deferred to a follow-up PR to keep this one small and shippable.

The fail-closed teardown path threw internal pty ids ("Failed to stop
local worktree terminals: <id>@@daemon") straight into the delete toast
and CLI. Replace both throws with an actionable sentence (retry, or
close the terminal) and log the pty ids for triage instead. Also strip
Electron's "Error invoking remote method '…': Error:" IPC wrapper in the
renderer delete path so the toast shows the message, not transport
plumbing.
@brennanb2025

Copy link
Copy Markdown
Contributor

Deep review follow-up pushed at 45b433897.

Found and fixed one Windows SSH relay fail-closed gap: after a graceful ConPTY close had not produced its native exit event, the 5s fallback retained the internal map entry but marked it disposed and emitted synthetic pty.exit. Targeted liveness therefore reported the PTY absent and desktop lease/runtime ownership could be retired before native death was verified. The relay now treats the initial ConPTY close as the only force-capable action, retains externally visible liveness until the real exit event, issues no second close on delayed destructive retries, and allocates no redundant Windows fallback timer.

Red/green regression now proves that before native exit: the owner remains counted, pty.hasPty remains true, no synthetic exit is emitted, and repeated immediate retries issue no second ConPTY close; after native exit, liveness becomes false.

Exact-head verification:

  • reliability gate: 15 files, 966/966 tests passed
  • full suite: 2,832 files passed, 6 skipped; 29,951 tests passed, 42 skipped
  • pnpm typecheck passed (node/CLI/web)
  • pnpm lint passed (pre-existing warnings only)
  • pnpm build:relay passed for Linux/macOS/Windows x64+arm64 and WSL
  • git diff --check passed

Physical Windows, native WSL, and live SSH remain the same explicit environment gaps documented in the PR.

…adopt main's stablyai#8706 for POSIX descendants

Reconciles the fail-closed worktree-teardown fix with main's newly-merged
teardown work so the two compose instead of collide.

Kept (the actual stablyai#8275 fix — Windows batch `worktree rm` killing the shared
daemon and unrelated ConPTY panes):
- fail-closed verified worktree teardown + legacy-daemon verification gate
- worktree PTY admission fence (no spawn races an in-progress deletion)
- Windows ConPTY verified teardown / daemon preservation (retain owner until
  native exit; never double-close ConPTY)
- support glue (owner cache, bounded stop concurrency, teardown timeouts)

Dropped the PR's parallel POSIX relay kill-path rewrite (pty-session-kill /
-membership / -exit-verification): main's stablyai#8706 (pty-descendant-termination)
already reaps detached agent descendants on POSIX, so a second freeze-sweep
engine that ran on every immediate shutdown was redundant, riskier scope.

Integrated instead:
- POSIX agent-descendant reaping comes from stablyai#8706, including inside our verified
  daemon force path (Session.forceKillAndDisposeSubprocessAndWait snapshots +
  terminates descendants before the verified root kill)
- daemon POSIX force-kill is a leader SIGKILL + await-native-exit shim
  (zombie-safe via existing exit waiters); relay POSIX teardown is a plain
  SIGKILL (SSH fail-closed enforced at the runtime liveness layer)

Follow-up: descendant-level POSIX verification for non-agent / pre-reparented
survivors on the delete path (leader-level fail-closed holds today).
@brennanb2025

Copy link
Copy Markdown
Contributor

Re-scoped to the #8275 core and rebased onto current main (head ebe0b6523).

While this PR was open, main merged several teardown fixes — most relevantly #8706 (STA-1800), which reaps detached agent-descendant processes on POSIX teardown. That collided with this PR's own POSIX kill-path rewrite. Rather than ship two parallel descendant-killers, I reconciled them so they compose.

Why this was still needed: I verified that #8275's actual bug — batch orca worktree rm --force on Windows killing the shared ConPTY daemon and unrelated split-pane sessions — is not fixed on current main. None of the merged teardown PRs touch that path; main's worktree teardown is still best-effort/fire-and-forget with no post-kill verification and no admission fence. So the core of this PR is the real fix and is kept.

Kept (the #8275 fix):

  • Fail-closed verified worktree teardown — Git removal is blocked until the worktree's terminals are proven stopped (local/daemon/SSH), plus the legacy-daemon verification gate.
  • Worktree PTY admission fence — a new spawn can't race an in-progress deletion.
  • Windows ConPTY verified teardown / daemon preservation — retain the session until native exit; never double-close ConPTY (the direct [Bug]: Terminal daemon dies after batch worktree removal, terminating unrelated split-pane sessions #8275 corruption).
  • Support glue (owner cache, bounded stop concurrency, teardown timeouts).

Dropped: the parallel relay POSIX kill-path rewrite (pty-session-kill / -membership / -exit-verification, ~600 lines). #8706 already handles POSIX agent descendants; a second freeze-sweep engine that ran on every immediate shutdown was redundant and the riskiest code in the PR.

Integrated instead: POSIX descendant reaping now comes from #8706, including inside this PR's verified daemon force path (Session.forceKillAndDisposeSubprocessAndWait snapshots + terminates descendants before the verified root kill). The daemon POSIX force-kill is a small leader-SIGKILL + await-native-exit shim (zombie-safe via the existing exit waiters); the relay POSIX path is a plain SIGKILL (SSH fail-closed is enforced at the runtime liveness layer).

Net: the PR shrank from ~104 files / +14k to 50 files / +5.2k, drops the highest-risk code, and no longer duplicates #8706. Typecheck, lint, and the full teardown matrix (4,666 tests, including #8706's descendant suite) pass.

Follow-up: descendant-level POSIX verification for non-agent / pre-reparented survivors on the delete path (leader-level fail-closed holds today). A live Windows batch-worktree rm smoke remains the recommended pre-merge check, since the reported bug is Windows-only ConPTY behavior that unit tests can't fully exercise.

@AmethystLiang
AmethystLiang removed their request for review July 15, 2026 23:28
@brennanb2025

Copy link
Copy Markdown
Contributor

Thanks for this, @bbingz — and I'm sorry it sat in review for as long as it did. 🙏

The important part: the bug you reported in #8275 is fixed, and it all ships in the next release. I ended up fixing the root cause across a couple of separate PRs, so I'm closing this one as superseded rather than landing a second implementation on top.

What went in:

So your exact scenario — batch orca worktree rm --force on Windows killing the shared daemon and unrelated sessions in another repo — is now covered end to end.

Really appreciate the detailed repro and the depth of work here. It pinned down a genuinely nasty teardown bug, and it directly shaped how thoroughly I ended up hardening that path. Closing as superseded by #8661.

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.

[Bug]: Terminal daemon dies after batch worktree removal, terminating unrelated split-pane sessions

3 participants