Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
0b380ad
Add lane-worktrees: the sweep that retires a lane's abandoned worktrees
brettheap Oct 5, 2026
8b49339
Test the sweep against every row of #162's disposition table
brettheap Oct 5, 2026
7d130ea
Place lane-worktrees as the seventeenth installed file
brettheap Oct 5, 2026
4f2acfb
Document the sweep in the lanes manual
brettheap Oct 5, 2026
7e58b98
Walk the estate for --links with scandir
brettheap Oct 5, 2026
8d2b71b
Keep a test run's bytecode, cache and temp out of the worktree
brettheap Oct 5, 2026
c4d97db
Name the repository's virtual environment on --install
brettheap Oct 5, 2026
c01ec43
Seed the workspace .gitignore with bytecode and cache rules
brettheap Oct 5, 2026
5458571
Refuse a workspace commit whose pathspec carries bytecode
brettheap Oct 5, 2026
23786c2
Run the estate report once a day from lane-start
brettheap Oct 5, 2026
7fdef8a
Report the estate's leftovers with sweep --all --dry-run --report
brettheap Oct 5, 2026
38e090f
Document the estate report and prevention at creation
brettheap Oct 5, 2026
bf066fd
Take Copilot round 1's defects in the sweep
brettheap Oct 5, 2026
ae17742
Document round 1's rules in the lanes manual
brettheap Oct 5, 2026
f4f4915
Merge branch 'feat/lane-worktrees-sweep' into feat/lane-worktrees-report
brettheap Oct 5, 2026
23d84da
Say what expiry_rows keeps now that #168 checks whole archives
brettheap Oct 5, 2026
da7bc59
Stop the suite's whole process group when a run ends
brettheap Oct 5, 2026
b0ea54e
Offer and seed site-packages/ with the other bytecode rules
brettheap Oct 5, 2026
b422d8f
Report duplicate clones with no named checkout, over one estate
brettheap Oct 5, 2026
3c0b188
Take Copilot round 2's defects in the sweep
brettheap Oct 5, 2026
892b02d
Merge branch 'feat/lane-worktrees-sweep' into feat/lane-worktrees-report
brettheap Oct 5, 2026
65f73ed
Refuse bytecode that is already staged, not only what add would stage
brettheap Oct 5, 2026
f5bccbd
Quote the venv paths in the command --install prints
brettheap Oct 5, 2026
dc7971c
Report what the report could not read
brettheap Oct 5, 2026
31db36f
Keep AGENTS.md and README.md within their line caps
brettheap Oct 5, 2026
30cc63a
Assert the flock file only where there is flock
brettheap Oct 5, 2026
5f4bd84
Switch --yes off until #170; keep the gate and exit contract honest
brettheap Oct 5, 2026
296e2f0
Document the --yes switch, lane-root ownership and porcelain escaping
brettheap Oct 5, 2026
9dd63f0
Merge branch 'feat/lane-worktrees-sweep' into feat/lane-worktrees-report
brettheap Oct 5, 2026
0cf1051
Run the suite in the foreground when stdin is a terminal
brettheap Oct 5, 2026
0b7a5c6
Never adopt a standalone clone under the lane's root
brettheap Oct 5, 2026
1484603
Merge branch 'feat/lane-worktrees-sweep' into feat/lane-worktrees-report
brettheap Oct 5, 2026
87da332
Merge remote-tracking branch 'origin/main' into feat/lane-worktrees-s…
brettheap Oct 6, 2026
45fa45d
Merge #168 at main 00971b9 (#121) into the report branch
brettheap Oct 6, 2026
4a79c8c
Fetch the register directly under --yes and refuse when it fails
brettheap Oct 6, 2026
040b12b
Pin the sweep's fetch refspec so --prune cannot delete local branches
brettheap Oct 6, 2026
d71569f
Refuse when a lane root or a registration list cannot be read
brettheap Oct 6, 2026
4b65c30
Walk every estate directory for dependents; a partial scan deletes no…
brettheap Oct 6, 2026
af80118
Read every other lane's control root before granting a tree
brettheap Oct 6, 2026
85365fd
Keep trees whose hidden edits, nested clones, tags or submodule refs …
brettheap Oct 6, 2026
8ec5a9a
Judge an ignored entry by its own name, not an ancestor's
brettheap Oct 6, 2026
d32be5d
Leave other people's open branches alone: rescue, and keep the remote
brettheap Oct 6, 2026
31bbfb1
Rescue before a prune, bundle what only a reflog names, self-check ea…
brettheap Oct 6, 2026
d4724a2
Run the push hooks where git-lfs is configured
brettheap Oct 6, 2026
1e583d1
Keep scratch that holds a tree or a repository
brettheap Oct 6, 2026
741b407
Check --live paths, want the writer count for own trees, stop on NOT …
brettheap Oct 6, 2026
c6ab9f8
Count --branches by origin's own ref, never by upstream or an open PR…
brettheap Oct 6, 2026
2eb5216
Remove a tmp.* only on proof, and read hidden processes as unknown
brettheap Oct 6, 2026
a6fb64b
Document what #170's fixes change in the sweep
brettheap Oct 6, 2026
7e1865a
Hold the existing cases to the register fetch, the untracked bundle a…
brettheap Oct 6, 2026
76011d9
Walk the whole estate only where a removal deletes a directory
brettheap Oct 6, 2026
08ad126
Refuse an inventory row that carries no id
brettheap Oct 6, 2026
5ba0c96
Fail closed when the bytecode check cannot read the pathspec
brettheap Oct 6, 2026
6404f1c
Document G9's id-less row and G13's failed read
brettheap Oct 6, 2026
6ef7c87
Read a .git file that is no pointer as no repository, not as unreadable
brettheap Oct 6, 2026
c550e89
Switch --yes back on: remove LANE_WORKTREES_ENABLE_YES
brettheap Oct 6, 2026
4eb9cbd
Refuse a clone's removal when a reflog directory under it cannot be l…
brettheap Oct 6, 2026
64710fb
Keep a tree whose submodule names an unpublished commit only in its r…
brettheap Oct 6, 2026
968f29d
Archive every ignored entry selected, whatever its ancestors are named
brettheap Oct 6, 2026
573a420
Never remove a cache directory that holds a repository
brettheap Oct 6, 2026
6436450
List an unmarked tmp.* that holds a repository, whatever its age
brettheap Oct 6, 2026
702c2df
Bundle a branch's reflog-only commits and self-check before --branche…
brettheap Oct 6, 2026
8fdb336
Hold back untracked files by the act's own snapshots, and keep untrac…
brettheap Oct 6, 2026
4ea93db
Read a clone's annotated tag through its peeled commit, and bundle th…
brettheap Oct 6, 2026
685cbe3
Document what #174's first Copilot round changes in the sweep
brettheap Oct 6, 2026
9eb473a
Keep a tree whose git directory keeps a submodule repository with wor…
brettheap Oct 6, 2026
b266ce2
Keep a tree whose submodule has a skip-worktree or assume-unchanged e…
brettheap Oct 6, 2026
72970e2
Pass over nothing but .git when proving a removal takes no nested rep…
brettheap Oct 6, 2026
fed0e5c
Read another lane's claims in every .lane-state the whole-estate walk…
brettheap Oct 6, 2026
4fba346
Never push a lane's commits onto a branch gh still answers OPEN, what…
brettheap Oct 6, 2026
a03f5df
Never prune a gone worktree's registration whose git directory keeps …
brettheap Oct 6, 2026
de45178
Keep a clone with a local git-lfs store when its commits would go to …
brettheap Oct 6, 2026
3cdd387
Count a suite's .lock or run.sh marker as provenance only when it nam…
brettheap Oct 6, 2026
de168db
Document what #174's second Copilot round changes in the sweep
brettheap Oct 6, 2026
c538ec1
Read a submodule repository kept under a git directory as that git di…
brettheap Oct 6, 2026
77db871
Keep a tree or registration whose submodule repository has an annotat…
brettheap Oct 6, 2026
499d2ab
Read the gone worktree's kept submodule repository in its regression …
brettheap Oct 6, 2026
1d3b321
Date a foreign clone with %cd --date=short, which every git reads
brettheap Oct 6, 2026
0d623aa
Read every lane's .lane-state for the report, and say "none found" on…
brettheap Oct 6, 2026
6a32842
Never read a failed register grep, a Latin-1 byte or an unlistable ro…
brettheap Oct 6, 2026
bffab3e
Never chmod through a symlink, ask a branch's PR by its branch, quote…
brettheap Oct 6, 2026
8e5a3c7
Read the bytecode check's git output in the C locale and unquoted, an…
brettheap Oct 6, 2026
ea26b52
Kill a TERM-deaf suite at the deadline, refuse a caller's --basetemp,…
brettheap Oct 6, 2026
4f6d122
Start the daily report private and deaf to its pane's hangup, and pro…
brettheap Oct 6, 2026
0292005
Document the report's lane-state parents and #170's other changes, an…
brettheap Oct 6, 2026
5b1fa79
Name an empty orphan or aging section unknown only beside a lane reco…
brettheap Oct 6, 2026
68be6c8
Start no daily report when its directories cannot be made 0700
brettheap Oct 6, 2026
e66195c
Name the sweeping session in the register line printed to write by hand
brettheap Oct 6, 2026
19e53be
Read the report's untracked listing like its ignored one: a failure i…
brettheap Oct 6, 2026
7ac5cbe
Never count the stray scan's own processes as strays, so a terminal r…
brettheap Oct 6, 2026
c3fd2a2
Merge remote-tracking branch 'origin/main' into feat/lane-worktrees-r…
brettheap Oct 6, 2026
fdb91ce
#179: skip the latin1-clone report case where the filesystem refuses …
brettheap Oct 6, 2026
2b7d91a
Adopt a tree by its Lane: trailer only when HEAD's own trailer is thi…
brettheap Oct 6, 2026
801d97d
List every tmp.* sandbox and never remove it; a bare repository is a …
brettheap Oct 6, 2026
4ea84c8
Read other lanes' claims in this sweep's estate only; with no tree, a…
brettheap Oct 6, 2026
465243e
Merge origin/feat/lane-worktrees-residue (#174 at 4ea84c8) into feat/…
brettheap Oct 6, 2026
8fa2d79
#179: assert the pid the test wrote, not the literal 999999, in the t…
brettheap Oct 6, 2026
6c79121
Merge origin/main (#174 squash) into feat/lane-worktrees-residue-test…
brettheap Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ and locked:
```sh
pat='^python3 -m pyt'"est" # split so it cannot match itself
while [ "$(pgrep -f "$pat" | awk 'END { print NR + 0 }')" -gt 0 ]; do sleep 20; done
flock "${TMPDIR:-/tmp}/openrepotools-pytest.lock" python3 -m pytest tests -q
PYTHONPYCACHEPREFIX="${XDG_CACHE_HOME:-$HOME/.cache}/openRepoTools/pycache" flock "${TMPDIR:-/tmp}/openrepotools-pytest.lock" python3 -m pytest tests -q -p no:cacheprovider
```

`awk` AND NOT `pgrep -fc`, which is what `tests/run.sh` does and for the reason
Expand Down
52 changes: 36 additions & 16 deletions docs/README-lanes.md
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ followed by the lock:
```sh
pat='^python3 -m pyt'"est"
while [ "$(pgrep -f "$pat" | awk 'END { print NR + 0 }')" -gt 0 ]; do sleep 20; done
flock "${TMPDIR:-/tmp}/openrepotools-pytest.lock" python3 -m pytest tests -q
PYTHONPYCACHEPREFIX="${XDG_CACHE_HOME:-$HOME/.cache}/openRepoTools/pycache" flock "${TMPDIR:-/tmp}/openrepotools-pytest.lock" python3 -m pytest tests -q -p no:cacheprovider
```

`awk` and not `pgrep -fc`: `-c` is not in every `pgrep` this toolset runs
Expand Down Expand Up @@ -3758,8 +3758,10 @@ started in the same second gets `<UTC>-2`) and never by a dry run:
**One register `NOTED` line per tree acted on** (`lanes-edit.sh log NOTED
lane:<lane>`), written after the act so it says what happened, naming the
disposition, the rescue and the archive; one more each for scratch, branches,
and caches with sandboxes. A line the register refuses is printed whole for a
person to write by hand, and the exit is 1. The line is the pointer that
and caches with sandboxes, each written as the session that swept
(`LANES_SESSION`, #170 E7). A line the register refuses is printed whole for a
person to write by hand - quoted for the shell (#170 E6) and naming that same
session (#179) - and the exit is 1. The line is the pointer that
outlives the archive.

**Retention: 90 days** (`sweep.conf`). `sweep --expire` lists archives older than
Expand Down Expand Up @@ -3831,11 +3833,17 @@ is 64, a `--live` path that names no tree included.

The net under every actor that never runs `lane-end`: **creation outpacing
closeout as a number that is read every day.** It reads every lane's #97
inventory and snapshot under `<estate>/.lane-state` (or
`$LANES_LANE_STATE_ROOT`), the workspace repository's register on
`origin/<branch>`, and the disk — **never the derived index** (Amendment 14
clause (b)) — and CHANGES NOTHING: no fetch, no index refresh
(`GIT_OPTIONAL_LOCKS=0`), no expiry.
inventory and snapshot in every `.lane-state` it can find (#170 G15, B8):
`<estate>/.lane-state`; beside every checkout the estate's shape walk finds,
so a nested checkout's lanes are read where #97 keeps them; under every
directory a walk of the whole estate finds, caches skipped, a plain directory
inside a repository included (`xFactory/xFactories/.lane-state`); beside every
lane's recorded `dir` in the register; and `$LANES_LANE_STATE_ROOT`. It also
reads the workspace repository's register on `origin/<branch>`, and the disk —
**never the derived index** (Amendment 14 clause (b)) — and CHANGES NOTHING:
no fetch, no index refresh (`GIT_OPTIONAL_LOCKS=0`), no expiry. A byte that is
not UTF-8, in a path or in the workspace's `.gitignore`, is carried as itself,
never an error (#170 G10-G12).

```sh
lane-worktrees sweep --all --dry-run --report [--estate <dir>] [--post <file> | --post <owner/repo>#<n>]
Expand All @@ -3844,16 +3852,16 @@ lane-worktrees sweep --all --dry-run --report [--estate <dir>] [--post <file> |
| section | what is listed |
|---|---|
| FOREIGN repositories | a CLONE inside a worktree container (`.lane-worktrees/<lane>`, `.claude/worktrees`, `worktrees`, `<x>-worktrees`), and a second clone of an origin the estate already has a checkout of: path, size, last commit, owner (the last `Lane:` trailer), and LOAD-BEARING with its dependents where something leans on it |
| Orphaned worktrees of ENDED lanes | trees an inventory records, and directories under `.lane-worktrees/<lane>`, of a lane whose snapshot is `CLOSED` or whose log's last lane line is `ENDED` or `RETIRED` — with the sweep that retires them |
| Orphaned worktrees of ENDED lanes | trees an inventory records, and directories under `.lane-worktrees/<lane>`, of a lane whose snapshot is `CLOSED` or whose log's last lane line is `ENDED` or `RETIRED` — with the sweep that retires them. Empty beside a record it could not read — a lane snapshot, an inventory, the register's lines, a directory the walk could not list — it says "none found in what could be read", never "none" (#170 B8); a failure that is no lane record (an ignored-file listing, say) is still a row of the last section but leaves "none" as it is (#179); so does Awaiting disposition |
| Unmerged branches | local branches of every checkout with no upstream, a gone upstream, a diverged one, or unpushed commits: tip, state, age, owner, and whether a worktree holds it. Never deleted; `main`, `master` and `rescue/*` are not listed |
| Root main divergence | a checkout whose local `main` is ahead of `origin/main`; where every changed path is under `handoffs/` or `lanes/`, the remedy is Amendment 4's — those live in the workspace repository and are pushed per write |
| Awaiting disposition | a `rescue/*` branch (local or on origin) and a DIRTY inventory tree whose git directory has been still for `aging_days` (14), with owner and age |
| Caches and sandboxes | `--include-caches`' and `--include-sandboxes`' rows across every lane, by size |
| Ignored directories over `ignored_report_mb` | every ignored directory of every repository at or over 50 MB (`du -sk`) |
| Evidence-shaped paths | untracked or ignored `junit*.xml`, `MANIFEST*`, `*-report.md`, `*REPORT*.md`, `canary-*`, `*-evidence`, `deployment-evidence` inside a repository, and a directory of reports beside the repositories — each a finding to move to the evidence root below |
| Evidence-shaped paths | untracked or ignored — an ignored FILE as much as an ignored directory (#170 item 9) — `junit*.xml`, `MANIFEST*`, `*-report.md`, `*REPORT*.md`, `canary-*`, `*-evidence`, `deployment-evidence` inside a repository, and a directory of reports beside the repositories — each a finding to move to the evidence root below |
| Sweep archives past retention | `sweep --expire`'s own dry run: what WOULD expire, and what is past retention but kept because its rescue left origin. Expiring stays an act (`sweep --expire --yes`) |
| Workspace repository hygiene | the workspace's `.gitignore` lines it lacks (with the one command that adds them), and bytecode already in its history (for a person's word: history is rewritten only on one) |
| Records the report could not read | a lane snapshot, inventory directory or record, or an inventory tree's `git status`, that could not be read — unknown is never absent, and the sections above may be missing what it holds |
| Workspace repository hygiene | the workspace's `.gitignore` lines it lacks (with the one command that adds them, its path quoted for the shell, #170 item 10), and bytecode already in its history (for a person's word: history is rewritten only on one) |
| Records the report could not read | a lane snapshot, inventory directory or record, or an inventory tree's `git status`, that could not be read; a directory the estate walk could not list, the estate root itself (#170 E10), the register's lane logs or its ENDED and RETIRED lines where the `git grep` failed (#170 G3), and a repository whose ignored or untracked files could not be listed (#179) — unknown is never absent, and the sections above may be missing what it holds |
| `status --all` findings | the estate command's ahead/behind, fork, shape-pin and parked-record lines, reported as what they are and never counted as dirt (`LANE_WORKTREES_STATUS` names the command when it is not beside this one) |

The summary table at its head also carries **the count of rescue branches** and
Expand All @@ -3867,8 +3875,14 @@ and before the launch: the first start of a UTC day takes the stamp
`${XDG_STATE_HOME:-~/.local/state}/openRepoTools/report-<YYYYMMDD>.stamp`
(an atomic `set -C` create, so two starts at once run one report), removes
older stamps, and starts the report DETACHED — stdin, stdout and stderr closed,
in a subshell that exits at once — over the start's own `$PROJECTS_ROOT`,
writing `…/openRepoTools/reports/<UTC>.md`. Every step either works or is skipped in
in a subshell that exits at once, under `nohup` and, where there is a `setsid`
(Linux), in a session of its own, so a pane killed after the stamp was taken
does not take the report with it (#170 E8) — over the start's own
`$PROJECTS_ROOT`, writing `…/openRepoTools/reports/<UTC>.md`. The report names
every path, branch and lane of the estate, so it is private: the state and
reports directories are 0700 and the report is written under `umask 077`
(#170 item 11); where they cannot be made 0700 no report starts and no stamp
is taken, so the next start tries again (#179). Every step either works or is skipped in
silence: the report never delays a start and never fails one. `--dry-run`
starts none; `LANE_WORKTREES_REPORT=off` is the switch (both suites set it).

Expand All @@ -3881,10 +3895,16 @@ The sweep retires what is already there; these keep more from arriving.
and roots every temporary directory of a run under
`${XDG_STATE_HOME:-~/.local/state}/openRepoTools/tmp/<UTC>-<pid>/`, which its
EXIT trap removes however the run ends — after stopping the suite's whole
process group, so nothing it started outlives the run root or the lock. With
process group (TERM, then KILL at ten seconds, and only then the leader
reaped, so a suite that ignores TERM cannot hold the lock for ever: #170 item
3), so nothing it started outlives the run root or the lock. With
a terminal on stdin the suite runs in the FOREGROUND instead, so `--pdb` or a
`breakpoint()` can read it (a background group would be stopped by SIGTTIN,
and the wrapper would wait on it holding the lock). A SIGKILL is the one end no trap sees;
and the wrapper would wait on it holding the lock); it is then in the
wrapper's own group, whose members are read before the suite starts, and
what is there afterwards that was not, and is no sibling of the wrapper, is
stopped the same way before the cleanup (#170 G14). `--basetemp` is the
wrapper's own, and a caller's is refused, 64 (#170 G8). A SIGKILL is the one end no trap sees;
the pid in the name is what lets `--include-sandboxes` tell its owner is gone.
* **One virtual environment per repository, outside the estate:**
`${XDG_CACHE_HOME:-~/.cache}/openRepoTools/venvs/<repo>/`. `openRepoTools
Expand Down
19 changes: 17 additions & 2 deletions lane-start
Original file line number Diff line number Diff line change
Expand Up @@ -4313,7 +4313,14 @@ lane_daily_report() {
ldr_state="${XDG_STATE_HOME:-$HOME/.local/state}/openRepoTools"
ldr_day="$(date -u +%Y%m%d 2>/dev/null || :)"
[ -n "$ldr_day" ] || return 0
mkdir -p -- "$ldr_state/reports" 2>/dev/null || return 0
# THE REPORT IS PRIVATE (#170 item 11): it names every path, branch and lane
# of the estate. Its directories are 0700, and it is written under umask 077.
# A directory that could not be made 0700 - one that already existed wider,
# on a file system or under an owner that refuses the chmod - runs NO report
# (#179, Copilot round 1): the stamp is not taken, so tomorrow's start tries
# again, and a report never lands where every local account could read it.
( umask 077; mkdir -p -- "$ldr_state/reports" ) 2>/dev/null || return 0
chmod 700 "$ldr_state" "$ldr_state/reports" 2>/dev/null || return 0
( set -C; : > "$ldr_state/report-$ldr_day.stamp" ) 2>/dev/null || return 0
for ldr_old in "$ldr_state"/report-*.stamp; do
[ "$ldr_old" = "$ldr_state/report-$ldr_day.stamp" ] || rm -f -- "$ldr_old" 2>/dev/null || :
Expand All @@ -4323,7 +4330,15 @@ lane_daily_report() {
# the report rather than left for it to find again.
ldr_estate=""
[ -d "$PROJECTS_ROOT" ] && ldr_estate="$PROJECTS_ROOT"
( "$ldr_bin" sweep --all --dry-run --report --post "$ldr_out" \
# A PANE KILLED AFTER THE STAMP WAS TAKEN DOES NOT TAKE THE REPORT WITH IT
# (#170 E8): the report ignores the hangup (`nohup`, everywhere), and where
# there is a `setsid` (Linux; macOS has none) it is in a session of its own,
# which no terminal's hangup reaches at all. Otherwise today's stamp would
# name a report that never finished, and none would run until tomorrow.
ldr_detach=""
command -v setsid >/dev/null 2>&1 && ldr_detach="setsid"
( umask 077
$ldr_detach nohup "$ldr_bin" sweep --all --dry-run --report --post "$ldr_out" \
${ldr_estate:+--estate "$ldr_estate"} </dev/null >/dev/null 2>&1 & ) 2>/dev/null || :
step "the estate's daily report is running, detached: $ldr_out"
return 0
Expand Down
Loading
Loading