Files
orca/src/main/runtime
NeilandOrca 5f642841fd fix(worktrees): stop terminals after external deletion (#11237)
* fix(worktrees): stop terminals after external deletion

* fix(worktrees): request teardown per caller and revalidate uncached

Two defects let the original fix silently strand PTYs:

- teardown rode the scan's coalescing promise, so any caller that joined an
  in-flight scan purged its renderer state without ever asking for a sweep;
  it now runs per caller against its own known-id snapshot, deduped on the
  request it actually produces so fan-out still shares one host sweep.
- the runtime's authoritative recheck was served from the 30s worktree-scan
  cache, which can still list a directory git already dropped. The renderer
  purges either way, so a stale miss leaked those processes permanently.

Co-authored-by: Orca <help@stably.ai>

* perf(worktrees): enumerate the host once per teardown sweep

An agent cleaning up N workspaces made killAllProcessesForWorktree issue one
full provider enumeration per missing worktree: O(N) relay round-trips carrying
O(N^2) rows. At 30 worktrees over an 80ms-RTT SSH link that is 30 scans and
~1.3s of stalled teardown; it scales linearly from there.

Share one point-in-time process list across the sweep — every worktree in it is
already known-missing, so a single snapshot answers all of them. A failed scan
is never shared: it falls back to a per-caller scan so one transient relay error
cannot suppress the sweep for the whole batch. Pinned requirePhysicalStop:false
since that path re-lists after shutdown and must not read a pre-shutdown snapshot.

Co-authored-by: Orca <help@stably.ai>

* test(worktrees): pin the disconnected-SSH no-teardown invariant

main's new directSshAuthority gate bails before any refresh when an SSH target
is not connected. That is exactly the #10562 safety rule — "host unreachable"
must never be read as "worktree deleted" — so pin it: a disconnected target
issues no teardown RPC and keeps its renderer state.

Co-authored-by: Orca <help@stably.ai>

* fix(worktrees): keep selector grammar intact when scoping by connection

resolveRepoSelectorForConnection matched the selector as a bare repo id, so an
explicit connection identity silently changed the grammar: `path:` and `name:`
selectors resolved to repo_not_found on that path alone, losing the whole sweep.
A connection identity should only *narrow* the candidate set.

Extract the selector matching both paths now share, and stop re-resolving an
already-resolved repo: teardown rescanned via `id:<repo.id>`, which throws
selector_ambiguous when an id is duplicated across hosts even though the
caller's own selector was unambiguous.

Reported as a P2 by Greptile (as redundant work); it is load-bearing.

Co-authored-by: Orca <help@stably.ai>

* fix(worktrees): keep the shared snapshot out of provider internals

The snapshot proxy passed itself as the Reflect.get receiver, so prototype
methods invoked through it ran with `this` bound to the proxy. A provider whose
own shutdown() re-read state via `this.listProcesses()` would then silently get
this sweep's cached snapshot instead of the live host — batching leaking past
the calls it was built for.

Bind non-listProcesses members to the target so only the sweep's own calls share
the snapshot. No shipped provider does this today; the point is that adding one
must not quietly change teardown semantics.

Raised by Greptile as an undocumented implicit constraint; closed structurally
rather than by comment.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-30 02:05:13 -07:00
..