Skip to content

lane-worktrees sweep: one act that archives, rescues and removes a lane's abandoned worktrees, branches, scratch and killed-suite sandboxes after an ungraceful swap — dry run first, rescue branch or bundle before any removal, never a live writer #162

Description

@brettheap

On Brett Heap's word of 2026-10-05 ("file both issues"). A swap that does not finish gracefully — a usage limit, a killed pane, a crashed harness — leaves the lane's writer worktrees where they stood: branches with unpushed commits, dirty trees, detached baselines, lane scratch directories and killed-suite sandboxes. Today nothing removes them: the protocol's worktree-safety rule (a swap never commits, pushes, stashes, resets or cleans) is right, and the result is that leftovers accumulate until a person does by hand what Codex did on 2026-10-03/04 — archive, verify, remove, record. This issue makes that procedure one act.

The act

lane-worktrees sweep <lane> [--dry-run | --yes] [--include-scratch] [--include-sandboxes] on PATH (placed by --install), also reachable as lane-end --sweep and offered by the resuming coordinator after its writer count. --dry-run is the default and prints the disposition table; --yes performs it; one register NOTED line per tree names the disposition and where the rescue is.

tree state disposition
a LIVE writer owns it (the coordinator's count, A17 Addendum 1 (i); #97's RUNNING with a live session) never touched
clean, branch merged into main remove worktree; delete local and remote branch
clean, branch pushed and unmerged remove worktree; the branch stays on origin (an open PR is untouched)
clean, unpushed commits push the branch as is; if the remote branch exists and diverged, push to rescue/<lane>/<slice>-<UTC>; then remove
dirty or untracked work git add -A + WIP commit on rescue/<lane>/<slice>-<UTC> (subject names the lane, the slice, the UTC and "swept, not reviewed"), push, then remove
detached HEAD, no commits beyond a main ancestor remove
detached HEAD with its own commits rescue branch as above, then remove
a worktree registration whose directory is gone git worktree prune
lane scratch (.lane-worktrees/<lane>/*-scratch, briefs/, bin/) with --include-scratch tar + sha256 under ${XDG_STATE_HOME:-$HOME/.local/state}/openRepoTools/sweeps/<lane>/<UTC>/, then remove
killed-suite sandboxes (/tmp/tmp.* and pytest-of-$USER/* whose owning pid is gone) with --include-sandboxes remove (they hold no work) — AND tests/run.sh gains an exit trap removing its own temp root, so this row shrinks

Rules: nothing is deleted that is not first on origin or in a git bundle under the sweeps directory (--bundle writes one per tree before removal, always for a rescue); a tree the sidecar does not know (#97's inventory) is reported as FOREIGN and left alone (another lane's, or a person's) unless --include-foreign and a word; --yes refuses while the lane's row says a session is live on another host (A18(b): unknown, never dead). The register line per tree is the recovery pointer; with the worktrees index table landed (the sibling issue) the sweep's result is visible from any workstation.

Not here

Deleting rescue branches (a later word; they are cheap). The managed path's own worktree claims (the 001 feature's).

Refs #97, #160, brettheap/new-workstation#48.

Lane: openRepoTools-3

🤖 Generated with Claude Code

Folded in on Brett Heap's word of 2026-10-05 ("fold the three into 162 and file the close-out gate")

From the cleanup analysis of the xFactory estate (ten causes; /workspace/projects/.repo-cleanup/*/FINAL-REPORT.md and the two IMPLEMENTATION-REPORTs of 2026-10-04/05): workspace creation outpaces workspace closeout. Three additions close the causes the table above did not reach.

--branches (causes 5: "131 and 79 local branch references")

Branches that have NO worktree are swept too: a local branch merged into main is deleted (local and, where it is ours and merged, remote); an unmerged local branch whose upstream is missing or diverged is LISTED with its tip, its merge-base distance and its last commit date, never deleted — a person disposes of it (push to rescue/…, or a word). A branch that an open PR names is never touched.

--include-caches (cause 3: "968 of 1,000 removals were dependency caches")

Under a lane's folders (.lane-worktrees/<lane>/**, .claude/worktrees/** of that lane's trees): __pycache__/, .pytest_cache/, .mypy_cache/, .ruff_cache/, node_modules/ and virtual environments (venv/, .venv/, any dir with pyvenv.cfg) are removed without archiving — they hold no work and are rebuilt. The convention this sweep assumes, written into docs/README-lanes.md: one virtual environment per repository, outside the estate (${XDG_CACHE_HOME:-$HOME/.cache}/openRepoTools/venvs/<repo>/), and suites run in ephemeral temp roots the runner removes on exit (the tests/run.sh trap above).

Retention of the sweeps directory (cause 10: "no consistent retirement destination")

Archives under ${XDG_STATE_HOME:-$HOME/.local/state}/openRepoTools/sweeps/<lane>/<UTC>/ carry a MANIFEST.sha256 and a DISPOSITION.md (what each entry was, why it was swept, the register line that records it). Retention: 90 days by default (sweep --expire lists and, with --yes, removes archives older than that whose every rescue branch is on origin); an archive whose rescue branch is gone from origin is never expired. The register line is the pointer that outlives the archive.

Two protocol lines (causes 2 and 4), proposed for the amendment text that ratifies this act

  1. A lane creates worktrees, never clones. Every tree a lane works in is a git worktree of the estate's checkout, registered in the lane's inventory (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). A standalone clone under the estate is FOREIGN to every sweep and is listed as a finding with its path, size and last commit, for a person's word.
  2. Scratch lives in the session scratchpad or the lane's state directory, never beside repositories. .lane-worktrees/<lane>/*-scratch, briefs/, bin/ are grandfathered into the sweep's archive-then-remove row; new scratch goes to ${XDG_STATE_HOME:-$HOME/.local/state}/openRepoTools/lanes/<lane>/scratch/ or the harness scratchpad.

The close-out gate

Filed separately as the lane-end gate (see the linked issue): lane-end runs this sweep's dry run and REFUSES to end a lane whose inventory still has trees, branches or scratch — the same way it refuses with open objects today — until lane-end --sweep --yes retires them. Landing and retiring stop being two acts.

Lane: openRepoTools-3

Folded in on Brett Heap's word of 2026-10-05 ("file the not prevented fixes") — relocation at creation, the nightly report, aging, expiry

Nothing new lands beside the code (causes 3 and 4, prevented at creation rather than at closeout)

  • tests/run.sh exports PYTHONPYCACHEPREFIX=${XDG_CACHE_HOME:-$HOME/.cache}/openRepoTools/pycache, adds -p no:cacheprovider, and roots its temp directories under ${XDG_STATE_HOME:-$HOME/.local/state}/openRepoTools/tmp/<run>/ with the exit trap above — so no __pycache__, .pytest_cache or sandbox appears in a worktree.
  • openRepoTools --install creates (or names) one virtual environment per repository under ${XDG_CACHE_HOME:-$HOME/.cache}/openRepoTools/venvs/<repo>/; README-lanes.md documents it as the convention; the sweep's --include-caches row shrinks to the legacy leftovers.

The nightly estate-wide report (the net under every actor that never runs lane-end)

lane-worktrees sweep --all --dry-run --report reads every lane's inventory (#97), the #161 index where configured, and the disk, and prints — or posts to the place Brett names (a file under the state dir, a GitHub issue comment, or the dashboard) — one table: FOREIGN repositories (path, size, last commit, owner from the Lane: trailer where any), orphaned worktrees whose owning lane has ENDED, unmerged branches with missing or diverged upstreams (age, owner), caches and sandboxes by size, rescue branches and dirty inventory trees older than the aging threshold, and sweep archives past retention. It is run by lane-start (once per day per workstation, guarded by a stamp file) and may be scheduled; it changes nothing. It prevents nothing by itself: it turns "creation outpacing closeout" into a number that is read every day.

Aging (cause 7)

A rescue branch or a dirty inventory tree older than 14 days (default; sweep.conf may change it) appears in the report under "awaiting disposition" with its owner and age until a person disposes of it.

Expiry runs itself (cause 10)

sweep --expire is run by lane-end's dry run (and by the nightly report) rather than by a scheduler; what expired is listed in the report.

Lane: openRepoTools-3

Folded in on Brett Heap's word of 2026-10-05 ("fold them") — the top-20 cleanup causes this sweep did not yet reach

Source: /workspace/projects/.repo-cleanup/xfactory-remaining-estate-20261005T130847Z/TOP-20-REASONS.md. Causes 1–6, 8 and 16 were already covered above; these rows cover the rest (cause 12 is filed in the Speckit repository; cause 18 in #163).

cause addition to this act
7 — coordination commits on a root main the nightly report flags any estate repository whose local main is ahead of origin by N commits with only handoff/register paths changed ("root main divergence"), with the remedy (Amendment 4: handoffs and the register live in the workspace repository and are pushed per write)
9 — pin differences read as dirt; 17 — stale tracking and unreported pins the nightly report incorporates status --all (the estate command's ahead/behind, fork, shape-pin and parked-record findings), so pin drift and stale tracking are reported as what they are and never counted as dirt by the sweep
10 — historical evidence mixed with current material ONE evidence root per repository, ${XDG_STATE_HOME:-$HOME/.local/state}/openRepoTools/evidence/<repo>/<UTC>-<slug>/, documented in README-lanes.md; the report lists evidence-shaped directories (JUnit, manifests, *-report.md, canary-*, deployment-evidence) found INSIDE the estate as findings to move
11 — superseded proposal/task variants keeping their own checkouts a planning checkout (a worktree whose branch carries only openspec/, specs/, ideation/ changes) whose OpenSpec change is ARCHIVED or marked superseded is retire-able: worktree removed, branch kept on origin; listed before removal; Brett's Triad Feature Amendments rule names where the surviving text lives
13 — squash merges hide completion by ancestry "merged" is decided by the register's LANDED line for the branch's PR, or by gh pr view <n> --json state,mergedAt where the branch has a PR, NEVER by git branch --merged alone; a branch with neither is listed as unmerged with its age
14 — clones as shared Git stores or local remotes FOREIGN detection also reads .git/objects/info/alternates and remotes whose URL is a local path; such a clone is LOAD-BEARING: never auto-removed, reported with every checkout that depends on it, and the remedy is git repack -a / re-pointing before any retirement; protocol line: a lane adds no local remote and no alternates
15 — pointers and shortcuts on obsolete locations --links: every symlink under the estate and every .git gitdir pointer (worktree .git files) is checked to resolve; broken ones are listed with their target and age; the handoffs link check above is one case of this
19 — generated bytecode committed into handoff attachments workspace-repository hygiene: brett-wip (and every <user>-wip) gets a .gitignore for __pycache__/, *.pyc, .pytest_cache/, node_modules/, venvs; lanes-edit.sh's handoff/attachment commit path REFUSES a pathspec that carries any of those; the report lists bytecode already in history for a person's word
20 — cleanup itself creating copies, references and archives every cleanup act goes THROUGH this sweep (one sweeps dir outside the estate, manifests, expiry, one register line per disposition) and never through ad hoc copies beside repositories; the report shows the sweeps directory's size and the count of rescue branches, so the cleanup's own footprint is watched

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