Skip to content

reapOrphanTabs

reapOrphanTabs(herdr, prevShellOnly?): Promise<ReapResult>

Defined in: src/tab-reaper.ts:114

Reconciliation sweep: close any usage-probe / review / namer / distill helper tab whose pane is a husk — an idle shell with no agent process running in it. The teardown paths (herdr.stop / start rollback) stop most leaks at the source; this is the durable safety net for husks they can’t reach — agents that crashed, or anything orphaned across a shepherd restart (which clears in-memory review tracking). Returns a ReapResult.

Husk signal = per-pane process liveness (ground truth), not list-absence. Under herdr 0.7 an exited helper agent leaves its pane alive as an idle zsh, and that pane STILL appears in agent list (#721) — so the old “absent from agent list ⇒ orphan” signal never fires. Instead we ask herdr for each helper pane’s foreground processes:

  • non-shell proc present (claude / node / node-MainThread / …) → live → spare (sparedLive).
  • process-info throws (transient read failure / quirk) → spare (sparedError).
  • empty proc list (undeterminable) → spare fail-closed (sparedError); we never reap on no evidence.
  • shell-only (procs.length > 0 && procs.every(SHELLS.has)) → husk CANDIDATE this sweep.

Two-sweep debounce. herdr’s own PTY is a zsh that runs the agent command, so a just-spawned agent is briefly shell-only during its pre-exec window. To avoid reaping that, a husk candidate is only closed when it was also shell-only on the previous sweep (its tabId was in prevShellOnly). A first-time shell-only sighting is recorded in the returned shellOnly set (caller threads it back in) but not closed.

panes() throw is fail-closed. If herdr.panes() itself throws it’s a transient herdr read failure on a supported herdr — we reap nothing this sweep and preserve the debounce set (return prevShellOnly unchanged) so a candidate isn’t lost mid-debounce. (A per-pane paneForegroundProcs throw is sparedError, see above.)

Closed tabs are closed in arbitrary order — herdr 0.7 stable ids (#569, e.g. w1:t1) don’t retarget on close, so close order is irrelevant.

ReapableHerdr

Set<string> = ...

Promise<ReapResult>