Skip to content

lane-end close-out gate: refuse to end a lane whose inventory still has worktrees, branches, scratch or caches — the dry-run sweep first, lane-end --sweep --yes to retire them in the same run; landing retires its own tree #163

Description

@brettheap

On Brett Heap's word of 2026-10-05 ("fold the three into 162 and file the close-out gate"), from the finding that workspace creation outpaces workspace closeout (the xFactory cleanup analysis: completed worktrees left after landing; branches outliving their purpose; scratch and caches beside repositories).

The gate

lane-end <lane> (and --retire) runs #162's sweep as a DRY RUN first and refuses to end the lane while its inventory still has anything to retire — worktrees (#97's sidecar inventory), their branches, local branches with no worktree that the lane created, lane scratch, caches — exactly as it refuses today while the lane holds open objects in its log. The refusal prints the disposition table and the one command that clears it:

lane-end: openRepoTools-3 still owns 3 worktrees (2 clean+merged; 1 dirty) and 5 branches — not ended.
  retire them:  lane-end openRepoTools-3 --sweep --yes      (rescue branches first; see the table)
  or review:    lane-worktrees sweep openRepoTools-3 --dry-run

lane-end --sweep --yes performs #162's dispositions (rescue before any removal; never a live writer; FOREIGN trees left alone), writes one register NOTED line per tree, and then ends the lane in the same run. --force-end ends the lane WITHOUT retiring (a word, logged with its reason) for the cases a person decides to keep a tree alive past the lane — the trees then show as orphaned in lanes --index --worktrees (#161) with the ended lane as owner.

Landing retires too

The shipped landing path — whatever command squashes a lane's PR with proof (today each lane's private squash-land.sh; a lane-land is the natural home, filed when that script is adopted) — removes the PR's worktree and both branches when the tree is clean at the landed head, as the private script does now, and records it. A landing that leaves a dirty tree says so and leaves it to the sweep.

Why a gate and not a reminder

The cleanup records show landing and retirement happening as separate acts for months, and the separate act not happening. lane-end already has the shape — refuse while open objects remain — so the gate adds one more thing it counts, from the inventory #97 already keeps.

Tests

Shell-suite cases: a lane with a clean merged tree refuses to end, ends with --sweep --yes, the tree and branches are gone, the register line names them; a dirty tree gets its rescue branch on the fake origin before removal; a live writer's tree is never touched and the lane refuses to end; --force-end ends and the orphan shows in the inventory; the refusal's printed command is the one that clears it.

Refs #162, #161, #97.

Lane: openRepoTools-3

🤖 Generated with Claude Code

Folded in on Brett Heap's word of 2026-10-05 ("file the not prevented fixes") — the inventory-only door and the guard checks

From the re-analysis of the ten cleanup causes: standalone clones (cause 2) and temporary work inside the estate (cause 4) were DETECTED by the sweep, not prevented. Two additions make the inventory the only door:

  1. lane-worktree add <slice> [--branch <name>] [--from <ref>] — the one verb a lane uses to make a worktree: it runs git worktree add AND records the tree in the lane's Crash-consistent lane worktree recovery: a lane now says WHERE its session stopped — RUNNING → SWAPPING → SWAPPED with a generation/operation fence, a machine-readable worktree inventory taken at every handoff, and a resume reconciliation that reports and resets nothing #97 inventory in one act (path, branch, base, lane, UTC). A worktree made by hand is still legal git, but it is FOREIGN to every sweep until lane-worktree adopt <path> records it.
  2. Two guard checks (in the UserPromptSubmit guard, through the register service of Amendment 22 act 2: the SQL register under lanes-edit.sh — mode=primary writers in one transaction each, the estate switch and lanes/STORE, the write journal and the renderer (one mirror commit per write), exit 69, fail-closed readers, lanes-index import/rollback, SERIALIZABLE claim order, and the agreement tests #165 once it exists, directly until then; both refuse with exit 2 and print the one remedy):
    • the session's cwd is a git repository under the estate that NO lane's inventory knows (a clone, or a worktree nobody recorded) → "this tree is not a lane's: lane-worktree adopt <path> or work in lane-worktree add …";
    • untracked, non-ignored paths exist directly under .lane-worktrees/<lane>/ or the estate root that are not inventory worktrees → "scratch belongs in ${XDG_STATE_HOME:-$HOME/.local/state}/openRepoTools/lanes/<lane>/scratch/ or the harness scratchpad; move it or lane-worktrees sweep --include-scratch".
      Both checks read only the inventory and git status --porcelain --ignored=no of the cwd's parent; neither reads the index (Amendment 14 clause (b)).

Tests: a hand-made clone under the estate refuses the prompt and the remedy adopts it; a stray scratch dir refuses until moved; an inventory worktree passes; the checks add under 50 ms to the guard.

Lane: openRepoTools-3

Folded in on Brett Heap's word of 2026-10-05 ("fold them") — cause 18, ignored scratch escaping git status

The guard's scratch check (and the sweep's) reads git status --porcelain --ignored — ignored material included — not the default porcelain that hides it. Ignored directories directly under .lane-worktrees/<lane>/, the estate root or a worktree root above a size threshold (default 50 MB; sweep.conf) refuse the prompt the same way untracked scratch does, naming the directory and its size and the remedy (lane-worktrees sweep --include-caches for caches/venvs; the scratch directory for the rest). The nightly report lists ignored directories over the threshold estate-wide.

Lane: openRepoTools-3

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions