Proposal (rationale)
rungs worktrees answers one question: which worktrees hold uncommitted work on a branch that has
already landed — “where work actually gets lost”, in its own words. F-057: when
git status --porcelain fails in a worktree, the catch sets dirty = false, so the row prints
merged · prunable, the label for the one state the command exists to warn about the opposite of.
The runner’s own rule for an unreadable state is that it blocks; this command reports it as safe.
Decision
accepted — 2026-09-06 under WI-085. A failed read is
an unknown with a reason, never a clean row.
Plan
Requirements
- A worktree row states
clean,dirtyorunknown; an unknown row carries the reason Git gave. - The command’s output never prints
prunablefor an unknown row and says how many rows are unknown and why. - Listing stays read-only;
land’s holder checks and recovery behaviour are untouched.
Impacts
src/concurrency.ts(WorktreeRow,worktrees()),src/cli.ts(cmdWorktrees), the existing regression intest/core.test.js.
Approach
Replace the boolean with a tri-state and keep the caught error’s first line as the reason. The
regression removes a linked worktree’s directory from disk — Git still lists it, and status in a
missing directory fails — so the failing read is real, not a fabricated result object.
Acceptance criteria / tests
- A linked worktree whose directory was removed reports
unknownwith a reason containing the Git error, and the CLI line does not containprunable. - The existing merged-and-dirty regression still passes with the new field names.
landtests are unchanged and pass.
Out of scope
- Repairing or pruning worktrees; the command reports and never removes.
- Any change to attribution or the compare-and-swap protocol.
Execution
Executed 2026-09-06 on feature/WI-089-truthful-worktree-state, cut from main bc8cc32 (WI-088
landed). WorktreeRow.dirty became state: 'clean' | 'dirty' | 'unknown' plus an optional reason
(Git’s first line); the catch that set dirty = false now sets unknown and keeps the reason.
cmdWorktrees prints an unknown row as status UNKNOWN with the reason beneath it, counts unknown
rows as “could not be inspected” with the sentence “Unknown is not clean”, and computes the prunable
count from state === 'clean' alone. land, preflight and session start do not read the row.
No other consumer of the boolean existed (grep over src/ and test/).
Deviations. None.
Review
Against each acceptance criterion, 2026-09-06, Windows 11, Node v22.22.3:
- Unknown with a reason.
test/core.test.js“worktrees keeps a failed status read unknown…” adds a linked worktree, removes its directory (Git still lists it), and assertsstate === 'unknown', a non-emptyreason, and that the row has nodirtykey. Through the CLI the row readsmerged · status UNKNOWN feature/vanished, the reason line is printed, “1 worktree(s) could not be inspected” appears, and the wordprunabledoes not. - Existing regression “worktrees reports merged-and-dirty…” passes with
statein place of the boolean, and still asserts the worktree survives listing. landuntouched. Noland,preflightorsession startcode changed; the full serial suite includes every land regression:node --test --test-concurrency=1 test/*.test.jsafter rebuild, 152 tests, 149 pass, 0 fail, 3 platform skips, 139 s.
node src/cli.ts check: 31 pass. Pending. The exact-SHA OS/Node matrix has not run: the branch
is not pushed.