Skip to content

ReapWorktreesDeps

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

Reviewer/critic disposable checkouts ({basename}-review-{tag}, created by worktree.ts:createDetached) whose teardown was missed — a crash, a shepherd restart that cleared in-memory review tracking, or a foreign-era basename whose repo is no longer configured — accumulate as dead dirs under .shepherd-worktrees. This is a disk-driven sweep that reaps them; it COMPLEMENTS plan-gate’s gcStaleReviewWorktrees (which is store-driven and only knows plan_gate spawns it still tracks) — it does not replace it. Runs SYNC (a boot + hourly maintenance pass, not on the typing hot path), consistent with that sibling.

Tag-shape match, basename-agnostic (guard d). Selection keys off the reviewer TAG SHAPE — a name ending in -review-(<8hex> | <uuid>-<8hex>) — NOT the basename. This is deliberate: a worktree minted under a now-defunct basename (tank-review-…, flowagent-review-…, pulse-review-…) whose repo is no longer configured would be invisible to any basename- or repo-scoped filter, yet is exactly the kind of orphan that strands forever. Matching the tag suffix alone catches those. The flip side (guards below) is that a USER prompt slugging to review-* yields {basename}-review-* too — so a hex-shaped suffix could in principle alias real user work; (d)‘s strict hex/uuid shape plus the session-path spare (e) keep that from being reaped.

Full spare/reap coverage matrix (an unowned candidate is reaped only if it survives every spare below):

  • pre-inflight begin() window — each reviewer service’s begin() does createDetached (mints the -review-<tag> dir on disk) and only LATER inflight.set then recordReviewerSpawn. In that window the dir exists, matches the tag shape, is NOT in protectedPaths, has NO reviewer_spawns row, and hosts no live claude yet — a mid-begin checkout. Covered by the directory-age guard: a candidate whose dir is younger than graceMs (or that can’t be stat’d → fail-closed) is spared. Checked BEFORE the scanAlive probe so a not-yet-running spawn is held by age alone.
  • owned in memory (protectedPaths) — paths a reviewer service currently holds. Spared REGARDLESS of age or /proc liveness. This is the #631 regression guard: a re-adopted plan-gate orphan has a DEAD reviewer claude AND an OLD uncompleted reviewer_spawns row, yet tick() still needs its worktree — age/proc heuristics alone would wrongly reap it. The caller unions the three reviewer services’ inflightWorktrees() into this set.
  • live store session (sessionWorktreePaths, guard e) — any path backing a live user session is spared even if its name happens to match the tag shape.
  • live claude under the dir (scanAlive) — one cheap /proc pass; a candidate hosting a live claude is spared (sparedLive).
  • recent uncompleted spawn (the graceMs grace) — a reviewer_spawns row with completedAt == null whose spawnedAt is within graceMs. INVARIANT: recordReviewerSpawn runs AFTER inflight.set, so a row NEVER precedes in-memory tracking — the grace therefore does NOT cover the pre-inflight window (the age guard does). It spares a recently-spawned reviewer whose path is not (yet/any longer) in inflight, e.g. across a restart before re-adoption, or a review/critic spawn that isn’t re-adopted.
  • old + ownerless — survives every spare above → reaped.

Too-young/unstattable and live-session spares are counted under sparedOwned.

Fully dependency-injected (no direct fs/proc/store calls) so it is unit-testable without a real filesystem, /proc, or store. The real wiring into index.ts is a separate task.

dirMtime: (path) => number | null

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

Dir mtime in epoch-ms, or null if it can’t be stat’d (→ fail-closed spare). Injected so the function stays I/O-free + unit-testable; the caller wraps statSync(p).mtimeMs.

string

number | null


graceMs: number

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

Grace window for a recent uncompleted spawn (spare if spawnedAt > now()-graceMs). Also the dir-age threshold: a candidate dir younger than graceMs is spared.


listDir: (parent) => string[]

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

Entry NAMES under a parent (default in caller = readdirSync); [] if unreadable.

string

string[]


listReviewerSpawns: () => object[]

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

Append-only reviewer-spawn rows (subset of ReviewerSpawnRow fields).

object[]


now: () => number

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

number


parents: string[]

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

Distinct .shepherd-worktrees dirs to sweep.


protectedPaths: Set<string>

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

In-memory reviewer-owned paths — spare regardless of age/proc (#631 guard).


remove: (worktreePath) => void

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

Worktree removal wrapper (worktree.remove).

string

void


scanAlive: (paths) => Map<string, boolean>

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

One-pass /proc liveness probe (scanClaudeAliveByWorktree).

string[]

Map<string, boolean>


sessionWorktreePaths: Set<string>

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

Live store session worktreePaths — spare (user-work guard e).