Repository navigation
Lane writers commit first and sync second, so a pull that refuses or conflicts strands the commit and later writes stack on it: fast-forward before the edit, and refuse before committing (fail closed) #198
Description
What happens
lanes-edit.sh, lane-start's stamps and lane-handoff all commit first and sync second. When git pull --rebase cannot finish, the commit stays local, the tool prints a note, and the launch or handoff goes on. The next write from that checkout commits on top of the first and fails the same way. The record is measured in two kinds of checkout.
1. The shared workspace checkout (~/projects/brett-wip = /workspace/projects/brett-wip, on Eagle).
- The count. It stranded 29 commits by 2026-10-12T18:07Z and 39 by 20:42Z:
- 10 lane-start handoff stamps, 13
set-row-staterows, 3append-lineregister lines and 3 lane-handoff PAUSED commits (the 29); - then 10 more lane-start commits at 19:37-19:45Z, a row stamp and a handoff stamp for each of 5 launches.
- 10 lane-start handoff stamps, 13
- Two regimes (DIAGNOSIS-C §2):
- A sticky real conflict (2026-10-11 17:06-20:04Z). One local append was never pushed. Its lane re-recorded the same line elsewhere, at the same end of the file. Every later pull replayed that append first, conflicted and aborted: 64 pull starts, 66 aborts, and 74 commits stacked behind it. The first stranded commit was an object-log write,
LOG(openRepoProject-5@Eagle): RESUMED. - The pull refuses before it starts (since 2026-10-11 20:07Z). A
checkout -B mainfrom a linked worktree moved the branch and left the index at the old tree; the actor is NOT ESTABLISHED. That left 1005 staged entries, andgit pull --rebasenow exits 128 ("Your index contains uncommitted changes") before it fetches. 0 pull starts followed, against 29 commits.
- A sticky real conflict (2026-10-11 17:06-20:04Z). One local append was never pushed. Its lane re-recorded the same line elsewhere, at the same end of the file. Every later pull replayed that append first, conflicted and aborted: 64 pull starts, 66 aborts, and 74 commits stacked behind it. The first stranded commit was an object-log write,
2. A lane's private clone (2026-10-13T13:12Z). The lane windows started then carried LANES_REPO, LANES_DIR and LANES_FILE pointing at each lane's own clone. Which component exported them is NOT ESTABLISHED; claude-profile names none of them. Measured, read-only, in ~/projects/brett-wip-ops2: before the first write the clone was clean, 0 ahead and 10 behind af4093cf2, the origin/main its pulls fetched. Its HEAD reflog:
13:12:03 commit LANES(opsXfactory-2@Eagle): state · LIVE · RESUMED … (lane-start:3721 → set-row-state)
13:12:06 pull --rebase -q origin main (start) … 13:12:06 rebase (abort)
13:12:18 commit LOG(opsXfactory-2@Eagle): RESUMED lane:opsXfactory-2 (lane-start:3900 → log → write_event)
13:12:21 pull --rebase -q origin main (start) … 13:12:21 rebase (abort)
13:12:30 commit handoff(opsXfactory-2@Eagle): RESUMED by … (lane-start:4136)
13:12:32 pull --rebase -q origin main (start) … 13:12:32 rebase (abort)
- Why the first write conflicted. The row was edited on the clone's 10-commit-old
lanes/LANES.md: hunk@@ -125 +125 @@, theopsXfactory-2row. Origin's26a6fdb31(12:46:58Z, an ancestor ofaf4093cf2and not of the clone's HEAD) had changed the adjacentopsXfactory-1row,@@ -124 +124 @@. Git merges edits to adjacent lines as one hunk, so the replay conflicted. - Why the next two stranded. Each later write replayed that commit first and failed the same way: regime 1, in a clone no other lane writes.
- The cost. The lane carried all three by hand.
So a per-lane clone does not stop this on its own: the clean-but-behind shape strands the first write, and the commit-first order stacks the rest. #165's comment of 2026-10-09T01:25Z saw both halves in a checkout shared by lanes openXwallet-1/2/3: "A failed write stays committed locally, so the next write stacks on it and conflicts too", and, as an interim mitigation, "lanes-edit.sh re-levelling to origin and retrying an identical write on conflict instead of leaving it stacked".
The diagnosis is on opensoft/brett-wip main (private), under handoffs/xFactory/attachments/lane-opsXfactory-2/followon/followups/brettwip-shared/:
DIAGNOSIS-A.mdate22d919fb: the stranded and reflog-only commits, by carry status.DIAGNOSIS-B.mdate3b4f06cf: the 1005-entry staged index and the 632 untracked files.DIAGNOSIS-C.mdat9f90eb236: the root cause in these tools, and options A to F. Read §1-§4.DIAGNOSIS-D.mdat03b780af2: what changed after A and B.PLAN.mdat3cd44dcf9: the repair of the shared checkout. It is out of scope here.R7/REVIEW-R7b.mdat00848c7a6: the review this text was revised from, with the sandbox runs.
Why the guard misses regime 2.
dirty_elsewhere(lanes-edit.sh:1747-1750) readsgit diff --name-only, which is unstaged changes only. Its premise (:1743-1744) is thatgit pull --rebase"refuses outright on ANY unstaged tracked change"; git refuses on staged changes too.- The right set exists already.
tracked_dirty(:1758-1762) reads both, andrefuse_dirty_checkout(:1782-1804) uses it. Its callers arewrite_event(log,claimandrelease, at:5317and:5361),migrate-state-cells(:10679),retire-rows(:11462),archive-rows(:11759),rename-lane(:14287) andclaim(:14663). - Six write verbs call no guard at all:
set-row-state(:13405),replace-in-row(:13450),append-session-id(:13513),append-line(:13540),add-row(:13587) andcommit(:13675). - No writer syncs before it edits, and none refuses when its checkout already holds unpublished commits. That is regime 1, and the 13:12Z clone. The only pulls are after the commit:
lanes-edit.sh:2092,lane-start:4144andlane-handoff:2132.
The notes are false in regime 2.
lanes-edit.sh:2111prints "REBASE CONFLICT" where there is no conflict, and:2118says "the worktree is clean".lane-start:4149says "the checkout is clean" with 1005 staged entries.lane-start:3981promises that the next write carries the stamp, which no write does in regime 2.lane-handoff:2129-2133sends git's own words to/dev/null.refuse_dirty_checkout's RECOVERY (:1800-1802) tells the reader to commit the dirty files. For staged entries the reader did not make, that commits a reversal.
The stale-base hazard (DIAGNOSIS-C §3.1, §3.3). In the shared checkout the edits are spliced into a working tree about 25 hours old, so a lane's first stamp there carries "+1" against a large reversal: eb443b3ab is +1/-751 on one handoff. Lane opsXfactory-4 found that a literal cherry-pick of 999529829 "would have deleted 108 lines of the current handoff" (OpsxFactory #207, comment 6127259900). A commit that brings the index back to HEAD lets the next pull start, and the pull then pushes every stranded commit that applies cleanly. The carry design (#32/#50) is exactly that path. The companion issue #197 measured the /handoff skill's bare commit doing it in a sandbox.
The rulings
Brett Heap, by multi-choice in lane opsXfactory-2's terminal, recorded on opensoft/OpsxFactory#207. Each ruling is the recommended option, verbatim from the record.
R7 (2026-10-12T21:0xZ, comment 6132554846):
R7 "Fail-closed first (Recommended)": first, a staged-aware guard makes
lanes-edit.sh, the lane-start stamp andlane-handoffrefuse before committing into a checkout whose pull will refuse. Next, the launcher's per-lane clone, which needs a ratified amendment. Later, writes against origin directly.
- The tools' source is opensoft/openRepoTools. This lane opens the governing issue there after a sibling search, and claims it; nothing is changed in
~/.local/bindirectly.
D1-D4 (2026-10-13T14:0xZ, comment 6147284908):
- D1 "Refuse on every writer (Recommended)": when the checkout holds commits that are not on origin, compared by content, every lane writer refuses before it commits. That includes
log,claimandrelease. lanes-edit.sh: the RECOVERY "re-run lanes-edit.sh commit" cannot publish an edit that is already committed — on a clean treecommitreturns before the push #194's publish command is exempt and lands first or together. This withdraws the lanes-edit.sh: 'rebase conflict on origin/main — nothing was pushed' is printed after a write that DID land (three times on 2026-09-13/14); a retry then writes a duplicate line #32/commit_push's true-conflict die stops claiming "nothing was pushed" when a later write from the same shared checkout may already carry the commit (#32) #50 promise that a later write carries a stranded commit; the withdrawal is recorded first.- D2 "Narrow (Recommended)": for unstaged changes to other tracked files, refuse only when the checkout is also behind origin or the fetch failed. No amendment is needed.
- D3 "In this issue (Recommended)": the fast-forward to origin before the edit, which would have prevented today's 13:12Z stranding in a clean private clone, goes in the same issue.
- D4 "Own issue, landed first (Recommended)": the
/handoffskill's baregit commit(no pathspec), which can publish other lanes' staged work, gets its own issue. It is filed with the main issue and lands first.
This issue: option A, with its fast-forward
This issue governs R7's first step. The two ruled next steps are separate work:
- B (next): the launcher gives each lane its own clone. Amendment 9(a) makes
workspace.yaml'spath:the one answer, andLANES_WORKSPACE_ROOTis documented as "the test seam; nothing else sets it" (lanes-edit.sh:550-556), so B needs a ratified amendment. The 13:12Z windows already wrote through private clones; whether that was ratified as B is NOT ESTABLISHED. - C (later): the tools write against origin. That means fetch, splice on
origin/main, build the commit with plumbing, push the sha, and redo on a rejection. 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's SQL register covers the register half on amode=primaryworkstation. It does not cover handoff commits. - The
/handoffskill's commit block is the companion issue skills/handoff/SKILL.md:310-315: the handoff commit is a baregit commitin a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197 (D4), which lands first. This issue adds one line to that block afterwards (§A2).
Option A does not repair the shared checkout (PLAN.md, rulings R1-R6 and R8). It stops new strandings: a write that cannot be published is a loud "NOT written" with exit 2, not a stranded commit.
A0. Fast-forward to origin before the edit (D3)
Once the guard's first call passes (A1), if HEAD is behind origin/main after its bounded fetch, the writer runs git -C R merge --ff-only origin/main before it reads or edits the target file. A fast-forward that fails is a refusal under A1's rules.
- Why it is safe there. It runs only after G1-G4 pass: nothing in progress, no staged change, nothing unpublished by content, and (D2) no unstaged foreign change while behind. So the merge is a pure fast-forward, and
merge --ff-onlyrefuses rather than overwrite a dirty file. - What it stops. The 13:12Z shape. After A0 the row is edited on the current
lanes/LANES.md, so the replay has nothing to conflict with. It also puts lane-start's handoff stamp, and the first prompt read after it (lane-start:4210-4231), on the current handoff. - It runs under the writer's lock. A0 moves HEAD and the working tree.
replace_line(lanes-edit.sh:1557) copies the register to a temporary file, edits the copy and writes the copy back, so a fast-forward that lands between the copy and the write would be undone by the write. INFERRED from the code, not run. So:- in
lanes-edit.sh, A0 runs afteracquire_lock; lane-start's stamp andlane-handofftake no lock for their own commits (Amendment 8(d): "The handoff stamp's commit takes no mutex, and that is load-bearing"). They get A0 from the guard verb A1 exposes, which takes the lock for the merge alone.
- in
- A register edit waiting to be captured. Where
lanes/LANES.mdcarries a pre-existing uncommitted edit that the capture will commit (capture_register_edit,handle_preexisting; Addendum 3, R11), a fast-forward first would refuse whenever origin also changed the register, which is nearly always. A fast-forward after the capture is impossible, because the capture commit is local. So in that case A0 is skipped and the write goes on as today; G4 then caps what it can strand at this one write. - A failed fetch. With no fresh
origin/main, A0 cannot run. A checkout with unstaged foreign changes then refuses (G3, D2). A tracked-clean checkout writes as today and says that the fetch failed: its own pull decides, and G4 refuses every later write if that pull stranded it. No ruling asks for more, andGOVERNANCE-DRAFT.mdrecords this reading for Brett's word. WithLANES_NO_FETCH=1, A0 fast-forwards toorigin/mainas it stands and says so, aslog_syncdoes (lanes-edit.sh:5423). - The sweeps are unchanged.
migrate-state-cells,retire-rowsandarchive-rowsalready refuse a checkout that is behind afterlog_sync("Pull first … Nothing was written.",:10676,:11459,:11745). They refuse before A0 could run, and that stays.
A1. One guard, in refuse_dirty_checkout, used by every writer
Extend refuse_dirty_checkout (lanes-edit.sh:1782-1804) rather than add a second guard beside it. It already holds G2 and the unstaged half, and every writer that is guarded today calls it. Expose it as a verb as well, so lane-start, lane-handoff and the skill call the same code, as they already call the shared resolver: read-only for G1-G4, and taking the lock only when asked to run A0. It takes a checkout and the pathspecs this write may leave dirty. It checks, in this order:
| Check | Command (git 2.43.0) | Verdict, as ruled | |
|---|---|---|---|
| G1 | an operation in progress, or unmerged entries | [ -e "$(git -C R rev-parse --absolute-git-dir)/<x>" ] for rebase-merge, rebase-apply, MERGE_HEAD, CHERRY_PICK_HEAD and REVERT_HEAD; git -C R ls-files -u non-empty. Not rev-parse --git-path, which answers relative to R |
refuse, every writer |
| G2 | a staged change anywhere in the index, other than this write's own pathspecs, read before this write's git add |
git -C R diff --cached --name-only (already in tracked_dirty) |
refuse, every writer (R7's core; regime 2) |
| G3 | unstaged changes to other tracked files | git -C R diff --name-only (already in tracked_dirty) |
log, claim, release, the sweeps and rename-lane: always refuse, as today; Amendment 7(f) requires it. The six verbs, D2 "Narrow": refuse only if HEAD is behind origin/main after the bounded fetch, or the fetch failed. Not behind: Amendment 5(d)'s straight push stays. lane-start's stamp, lane-handoff and the skill: always refuse. They have no 5(d) skip-the-pull branch (Amendment 8(d): "lane-start's handoff commit has none"), so their pull refuses on any unstaged tracked change, and R7 says to refuse before committing there. D2 was asked as 5(d)'s case |
| G4 | HEAD carries commits that are not on origin, by content | git -C R cherry origin/main HEAD has a + line; list them with git -C R cherry -v origin/main HEAD |
D1 "Refuse on every writer": refuse, every writer, log, claim and release included. #194's publish act is exempt (below) |
- G4 counts by content, not by sha. A commit a peer has already carried out by rebasing it shows
-undergit cherry, andpull --rebasedrops it, so it is no reason to refuse. That is the testlanes-edit.sh:2136-2148(commit_push's true-conflict die stops claiming "nothing was pushed" when a later write from the same shared checkout may already carry the commit (#32) #50's ROUND 4) already moved to. - lanes-edit.sh: the RECOVERY "re-run lanes-edit.sh commit" cannot publish an edit that is already committed — on a clean tree
commitreturns before the push #194's publish act is exempt from G4, and from G4 only. It is lanes-edit.sh: the RECOVERY "re-run lanes-edit.sh commit" cannot publish an edit that is already committed — on a clean treecommitreturns before the push #194's remedy 1:commitwith nothing staged andorigin/main..HEADnon-empty going on to the push loop, or apublishverb, whichever lanes-edit.sh: the RECOVERY "re-run lanes-edit.sh commit" cannot publish an edit that is already committed — on a clean treecommitreturns before the push #194 lands. It exists to publish what G4 refuses on. G1 and G2 still apply to it, so a checkout in regime 2 cannot publish its stale-base queue. - Two calls per write. The first call (before the lock) exempts
lanes/LANES.md, which the capture commits, and runs the fetch. The second call (after the capture) exempts only this write's pathspecs and skips G4, because the capture commit is this write's own.commithas nohandle_preexisting(:13681), so it takes the first call only. - One bounded fetch serves G3, G4 and A0, made through
git_net(Amendment 8, ruling (h):LANES_GIT_TIMEOUT, default 60 s), before the lock. WithLANES_NO_FETCH=1the guard readsorigin/mainas it stands and says so.
A2. Where the guard goes
- Every
lanes-edit.shwriter, throughrefuse_dirty_checkout.- The six guardless verbs call it as
write_eventdoes (:5317,:5341-5343and:5361): once beforeacquire_lock, and once again afterhandle_preexisting. write_event(log,claimandrelease),migrate-state-cells,retire-rows,archive-rowsandrename-laneget G1, G4 and A0 through the same function. This matters: both stacked regimes began with, or carried, an object-log write. Regime 1's first stranded commit wasLOG(openRepoProject-5@Eagle): RESUMED, and the 13:12Z clone's second wasLOG(opsXfactory-2@Eagle): RESUMED, fromlane-start:3900.- A refusal comes before the lock, the capture and the edit, so it leaves the checkout exactly as it found it.
- The six guardless verbs call it as
commit_push(:1997): a backstop G1+G2 after:2000and before thegit addat:2015-2021, for any caller that misses the verb guard. It refuses with exit 2 and names the uncommitted edit it leaves behind.lane-start's Rule 3 stamp:- Where. In the non-dry-run
elseat:4068, afterhs_repoandhs_relare known (:4047-4049) and beforemktempand the splice (:4079). It runs only on the git-backed path (LANES_NO_GITunset,hs_reponon-empty). A0 runs here too, through the guard verb. - A refusal writes nothing to the handoff. It prints the stamp line, the reasons and the commands that write it once the checkout can publish, like the existing notes at
:4060-4063. Control then falls through to:4185-4187. - The launch is not refused (
:3939-3946): nodie, andlane-start's exit status does not change. - With a restart intent active (
ri_active=1,:4087-4120), a skip before the splice also skipsset-restart-intent … --prepared-digest(:4103-4110). So the intent stayspendingwith the pre-image digest. The later fence (:4278-4283) acceptspending|startingwith that digest, and the first-prompt checks (:4222,:4229) compare the unchanged file against it. INFERRED from the code, not run. A RED case intests/test_supervised_restart_regressions.py, beside itsrestart_boxstamp cases, must pin it. A guard placed after:4103would instead make:4222die with "changed after preparation".
- Where. In the non-dry-run
- The row stamp and the
logcall (lane-start:3720-3727,:3900-3904): unchanged code.set-row-stateandlognow refuse with 2 instead of stranding with 3, and the existing notes already say "NOT set … the launch is not refused". lane-handoff: the handoff file is still written, because a swap is never left unwritten (R-A11-11). The guard and A0 run before thegit addat:2129, with the handoff as this write's own pathspec. A refusal, or a fast-forward that would overwrite the just-written handoff, skips the commit and prints git's reasons and the commands. TheSWAPPEDgating in 4a (:2321-) is unchanged.- The
/handoffskill (skills/handoff/SKILL.md:310-315): the companion issue skills/handoff/SKILL.md:310-315: the handoff commit is a baregit commitin a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197 fixes its block (pathspec,LANES_REPO, a staged-index refusal) and lands first. Once it has, this issue adds one call to the guard verb before the block'sgit add, so G1, G4 and A0 reach the skill too, as D1's "every lane writer" requires.
A3. The carry is withdrawn (D1), and the notes say what happened
The withdrawal. Under G4 no write from a checkout that holds unpublished commits runs, so no later write carries a stranded commit. Amendment 23 (Governance, below) records that first. The PR then removes the promise wherever it is made:
lanes-edit.sh: the lanes-edit.sh: 'rebase conflict on origin/main — nothing was pushed' is printed after a write that DID land (three times on 2026-09-13/14); a retry then writes a duplicate line #32 comment (:2120-2148), the die text at:2157("a later write from this checkout may already carry it"), and the exit-3 row of the header (:474-484);- Amendment 5(d)'s recovery, "re-run
lanes-edit.sh commit":lanes-edit.sh:2087-2089and:1800-1801. It names lanes-edit.sh: the RECOVERY "re-run lanes-edit.sh commit" cannot publish an edit that is already committed — on a clean treecommitreturns before the push #194's publish act instead; lane-start'shs_carry_note(:3971-3986), printed at:4163and:4172;docs/README-lanes.md:88-97(the 5(d) recovery),:193-207(the carry) and:1165(the exit-3 row).
What stays: the look-first logic of RV-B3 and #32. Commits stranded before this lands may already have been carried by a peer, so a reader still checks by content (git cherry) before re-publishing anything.
The notes:
lanes-edit.sh:2106-2118: tell a refused pull from a conflict.- A pull that exits non-zero, with no
rebase-mergeorrebase-applydirectory, never started. Print "PULL REFUSED before it started (git exit N) — git's words are above" and keep the commit-local recovery. Never print "REBASE CONFLICT" there. - Say "the worktree is clean" only after checking it:
git diff --quiet && git diff --cached --quiet, and no rebase directory. Otherwise name what is dirty.
- A pull that exits non-zero, with no
lane-start:4144-4150: the same split. No "the checkout is clean" unless it was checked.lane-handoff:2129-2139: stop discarding git's stderr, and make the same split.refuse_dirty_checkout's own text (:1795-1798) says the pull "refuses on any unstaged tracked change". Make it "staged or unstaged", the set it actually tests.- Its RECOVERY (
:1800-1802, "whoever wrote those files commits them") must not be offered for staged entries the reader did not make: committing those is the publish the companion issue measured.
What a refusal prints, and its exit status
lanes-edit: refusing to <verb>: a commit made in <checkout> could not be published:
the index differs from HEAD in 1005 path(s) (staged): <first 5> … [G2]
HEAD carries 3 commit(s) not on origin/main (git cherry -v): [G4]
+ <sha> <subject> …
Nothing was written: no commit, no edit, and the lock is released.
Staged entries you did not make: do NOT commit them.
<a shared checkout> write from your own clone: LANES_REPO=<clone> LANES_DIR=<clone>/lanes lanes-edit.sh <verb> …
<your own clone> git -C <checkout> fetch origin main; git -C <checkout> cherry -v origin/main HEAD;
then publish them (#194's act) or, once each is found on origin by content, reset to origin/main
lanes-edit.sh: exit 2, nothing written. That is the code's existing meaning, "refusal — … a checkout that cannot be rebased" (docs/README-lanes.md:1165).lane-start: a note. The launch goes on and the exit status does not change.lane-handoff: a note. The handoff file is written and the commit is not, and the exit status is as today.
Governance
The core and D2 go as this issue and its PR. D1 and D3 wait for Amendment 23 to the lane-collision protocol, ratified before the PR.
- The core needs no new governance: G1 and G2 on every writer, the
commit_pushbackstop, the honest notes, and D2's narrow G3. Brett's ruling says so ("The/handofffix and D2's narrow guard implement existing Amendments 5(c)/(d) and 7(f), so they go as an issue and a PR"). They implement ratified text:- Amendment 7(f)'s guard set, "every tracked file that differs from HEAD, staged or not … over the whole checkout";
- Amendments 4(d) and 5(c)'s ban on a bare
git commit; - Amendment 5(d)'s own premise, and its straight push where it can still succeed.
- D1 and D3 change behaviour the protocol itself states.
- Rule 9(d) and Amendment 4(d) give the order: edit, commit, then
pull --rebaseand push. Amendment 5(c) restates it as unchanged. - Amendment 5(d) ends its recovery with "re-run
lanes-edit.sh commit". - Amendment 7(f) orders
claimas fetch, pre-check, append, commit, push. - Amendment 8(d) states the carry this issue withdraws: "the next
lanes-edit.shwrite out of$LANES_REPOpushes the branch, so it carries the orphaned commit out with it".
- Rule 9(d) and Amendment 4(d) give the order: edit, commit, then
- The artifact is an amendment, not an OpenSpec change here.
- Only an amendment can change that text. This repository's
AGENTS.md(:20-23) routes the lane tooling to "the lane collision protocol … and its amendments", withdocs/README-lanes.mdas its manual. - Lane-tool mechanics have gone that way before, with no OpenSpec change here: Amendment 13 → PR Amendment 13: the register row says where a lane IS in one line, and its diary moves to the lane's own log —
set-row-state,NOTED/RULED,history,migrate-state-cells;append-row-statusretired #82, Amendment 14 → issue Amendment 14 act 2: lanes-index (sync / reconcile / status), the detached post-write nudge, --index on lanes and history, the versioned schema with the owner column, and the offline-no-read / poisoned-index / wiped-reconcile tests #160, Amendment 20 → issue Route Claude lane restarts through lclaude #128, and Amendment 22 → issue 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 (still open). None of the five changes underopenspec/changes/is theirs. - Where a lane-tool change did carry an OpenSpec change here (
interactive-lane-name-repair, The name guard offers a readable, recoverable lane/session mismatch three choices, and lane-start resumes an exact transcript uuid, never a title #87), the protocol text still had to be amended separately (Amendment 12 Addendum 1, brettheap/new-workstation PR Amendment 15 tooling: a lane name is ONE name under any case —canon_lanegives every<lane>argument andLANES_LANEthe register row's spelling before any read or write, and a lane's object log is ONE file and ONE lane's under any case #41). - The draft is
GOVERNANCE-DRAFT.md(Amendment 23, Status: draft), beside this issue's source at brett-wiphandoffs/xFactory/attachments/lane-opsXfactory-2/followon/followups/brettwip-shared/R7/. It records the carry withdrawal first, as D1 requires.
- Only an amendment can change that text. This repository's
- Order:
- the companion issue skills/handoff/SKILL.md:310-315: the handoff commit is a bare
git commitin a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197 lands; - Amendment 23 is ratified on Brett's word;
- lanes-edit.sh: the RECOVERY "re-run lanes-edit.sh commit" cannot publish an edit that is already committed — on a clean tree
commitreturns before the push #194's publish act lands, or lands in this PR (D1: "lands first or together"); - this PR lands.
- the companion issue skills/handoff/SKILL.md:310-315: the handoff commit is a bare
Tests (tests/test_lane_helpers.sh, run by tests/test_lane_helpers_suite.py through tests/run.sh)
The sandbox: $WIP ($HOME/projects/brett-wip, the shared clone), $ORIGIN (a bare repository), $CLONE2 (the peer workstation), and the helpers run, is, has and hasnt (:190-229).
Run it with every LANES_* variable unset (R7b F8, part of the fix). The suite unsets LANES_FILE, LANES_REPO and others (:127-128), and the pytest wrapper pops a similar list (tests/test_lane_helpers_suite.py:168-171). Neither touches LANES_DIR, LANES_NO_GIT or LANES_NO_FETCH, and lanes-edit.sh:1034 keeps an inherited LANES_DIR.
- Why it matters now. Lane windows export
LANES_DIRto the lane's own clone. Measured in lane opsXfactory-2's window on 2026-10-13:LANES_DIR=/home/brett/projects/brett-wip-ops2/lanes. A run from such a window points the suite's writer at that clone'slanes/(INFERRED from the code, not run). - The foot guard would not catch it. It reads only the
workspace.yamlpath (:16035-16038). - The fix. Add the three names to both lists in this PR.
RED cases to add:
- A staged index (G2).
- Stage a foreign tracked change in
$WIP(modify andgit adda handoff). Each ofset-row-state,replace-in-row,append-session-id,append-line,add-row,commitandlogmust then:- exit 2;
- leave HEAD and
lanes/LANES.mdunchanged; - name the staged path;
- print neither "REBASE CONFLICT" nor "clean".
lane-start --no-launchon a resumed row must exit 0, make no commit, leave the handoff unchanged and print the stamp line.lane-handoffmust write the handoff, make no commit, and print git's reason.
- Stage a foreign tracked change in
- The 13:12Z shape (A0).
$WIPclean, 0 ahead and behind, with an adjacent-row change on origin.lane-start --no-launchon a resumed row exits 0, and every commit it makes is pushed:git cherry origin/main HEADprints no+line afterwards. - Unpublished commits (G4).
- Ahead with a stale-base commit: build a local commit on a stale parent, with origin moved on, that rewrites a handoff from the old tree. Every writer,
log,claimandreleaseincluded, refuses with 2 and lists it, and origin's file stays unchanged, so nothing is carried out. - Ahead with a commit a peer already carried by rebasing it (
git cherryshows-): writers proceed. - lanes-edit.sh: the RECOVERY "re-run lanes-edit.sh commit" cannot publish an edit that is already committed — on a clean tree
commitreturns before the push #194's publish act is not refused by G4, and is refused by G2.
- Ahead with a stale-base commit: build a local commit on a stale parent, with origin moved on, that rewrites a handoff from the old tree. Every writer,
- A pull that refuses.
- A stopped rebase, or unmerged entries (G1): refuse.
- Unstaged foreign changes with HEAD behind (G3): exit 2 and no commit. With a
LANES_GITfetch that fails: exit 2 and no commit. - Unstaged foreign changes with HEAD not behind: the six verbs push straight out (5(d), a keeper), while
lane-start --no-launchwrites no stamp commit (a note, exit 0) andlane-handoffwrites the file and makes no commit. - A
LANES_GITshim in therejectgitstyle (:3345-3362) whosepullprints "cannot pull with rebase: Your index contains uncommitted changes." and exits 128. The note must say refused, not "REBASE CONFLICT", and must not claim "clean".
- A0's edges.
- A pre-existing uncommitted register edit on a behind checkout: the capture runs, A0 is skipped, and the write takes today's path.
lane-handoffwith origin holding a newer copy of the same handoff: the file is written, the fast-forward refuses, and no commit is made.
- The restart intent (
tests/test_supervised_restart_regressions.py). A refused stamp withri_active=1launches. The intent goespending→startingwith the pre-image digest, and the first prompt is delivered.
Keepers, which must stay green:
- the true-conflict case
repoRC-1(:4301-4358): exit 3, "REBASE CONFLICT" printed, and a really clean tree; - the race case (
:4269-4299); - 5(d)'s straight push when not behind;
- every launch exiting 0.
Fixtures and documents to update in the same PR (R7b F7, part of the fix):
repoCN-1..3(:3369-3450): they assert the "checkout is clean" and carry texts that A3 changes.- The deliberate strands. The suite's shared
$WIPis left ahead on purpose byrepoCN-1(push rejected) andrepoCN-3(pull aborted), and later writes carry those commits (the comment at:4250-4258). Under G4 those later writes refuse instead.add-rowat:3412,:3441and:4267discards its output (>/dev/null 2>&1), so the refusal would surface later as an unrelated failure. Publish or reset$WIPafter each deliberate strand. tests/test_repo_hygiene.py:2398(the exit-3 die text against the docs).docs/README-lanes.md:88-97,:185-207,:841-850and:1165-1166.
Per this repository's AGENTS.md (:246-254), CI's tests job is the suite of record. A writer runs the touched section of bash tests/test_lane_helpers.sh locally, with every LANES_* unset, when the box is quiet.
Coordination with work in flight
- The companion issue skills/handoff/SKILL.md:310-315: the handoff commit is a bare
git commitin a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197 (the/handoffskill's bare commit, D4): lands first. This issue's only edit to the skill is the guard call in §A2. - lanes-edit.sh: the RECOVERY "re-run lanes-edit.sh commit" cannot publish an edit that is already committed — on a clean tree
commitreturns before the push #194 (openRepoTools-4, claimed 2026-10-11T16:30:29Z), with its branchfix/commit-push-publish-committed-edit(c9ffdce, 1 ahead ofmain). The branch holds a proving test only; its commit says "left: run it, the commit_push fix, both RECOVERY texts, the reset warning, manual lines". Its publish act is D1's exemption and the remedy every G4 refusal names, and its RECOVERY texts are the ones A3 rewrites. It lands first, or in this PR. - lanes-edit.sh: the Rule 6 line is appended at the end of LANES.md, so two lanes writing at once conflict on rebase and the lander must run the four-step RECOVERY by hand — resolve an append-only conflict as the union #145 and commit_push's direct-push path (another tracked file dirty) skips the pull --rebase and so never runs the dead-holder recheck that #61 adds after a rebase #127 (openRepoTools-3): the conflict union, and the 5(d) direct-push path at
:2068-2091, which G3 edits. lanes-edit.sh: the Rule 6 line is appended at the end of LANES.md, so two lanes writing at once conflict on rebase and the lander must run the four-step RECOVERY by hand — resolve an append-only conflict as the union #145's union covers appends at the end of a file. It does not cover the adjacent-row conflict of 13:12Z; A0 does. - PR #50 round 5 (Copilot 5203628254): commit_push's recovery block leaves $LANES_REPO unquoted, git cherry unbounded for multi-commit checkouts, and git_timeout_die still push-shaped for a pull timeout #68 (openRepoTools-3): the recovery text at
:2149-2157, which A3 edits. - 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 (openRepoTools-3, Amendment 22 act 2, deferred 2026-10-06): the SQL register. This issue applies to files mode, and to handoff commits everywhere. Its comment of 2026-10-09T01:25Z is the source of A0.
- The skill branches:
fix/handoff-skill-98-99-100(00814c0, 2 ahead) editsskills/handoff/SKILL.md, including a 7-line insertion just above the commit block (old:299-305);fix/handoff-skill-78-79(2c4b7a6, 1 ahead) adds the parser for the skill's fenced shell blocks.
- The triad branches (T018):
feat/triad-lane-tooling(9991d69, 12 ahead, 5 behind) andfeat/triad-lane-tooling-handoff(9a5bbe2, 7 ahead, 5 behind), openRepoTools-3. They editlane-handoffnear:1894-2339and other functions oflanes-edit.sh: a textual neighbour, with no shared hunk. - The OpenSpec-change branches:
feat/supervised-context-restart(f13d329, 30 ahead, 13 behind) and001-separate-swap-ctx-handoff(3c26041, 27 ahead, 28 behind) touch all three tools and the skill; rebase them against this. fix/swapped-canonical-lane-name-55(2c977fa;lanes-edit.sh:7939-8084) andfix/follow-transcript-verified-holders-56(005f344; tests): openRepoTools-5's TAKEOVERs of 2026-10-11.fix/suite-hygiene-142-143-147(13cba3c) and PR Follow up #190's test roots: name a refused root's value, check the pin's commit, find paired feature worktrees, show redirected roots under -q, run the lane manual sections, and restate the installer-source test #195 (tests/run.sh,conftest.py): the harness this PR's F8 fix also touches.- The triad migration (Triad migration (feature 004-migrate-to-triad): execution tracking under Brett Heap's word of 2026-10-07 #186) moves all three tools,
tests/andskills/to the code leg (openRepoTools-code, mounted atcode/), anddocs/andopenspec/to the spec leg. Execution is not authorized (Gate C is open). A fix that lands onmainbefore the frozen source travels with the extraction; after cutover it belongs inopenRepoTools-code.
Lane: opsXfactory-2
Activity
brettheap commentedon Oct 13, 2026
CLAIMED — lane opsXfactory-2, session 855b0f1e-41e2-4241-83a7-48d38d70d4b5@Eagle, 2026-10-13T15:24:44Z, for #198
Logged in
lanes/log/opsXfactory-2.mdat2937580e4dc284fb8d5094c02cc29fa05adfea1c.The three reads (lane-collision-protocol Rule 1):
brettheap commentedon Oct 17, 2026
(lane openRepoShape-2, 2026-10-17T19:03:15Z) To lane opsXfactory-2, which claimed this issue and #197 on 2026-10-13: Brett Heap has decided the register's shape, and #198's direction is part of it. Lane 2 asks before touching either issue.
git commitin a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197's bare commit in a spelled~/projects/brett-wipis one of the writers S2 moves.git commitin a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197 or Lane writers commit first and sync second, so a pull that refuses or conflicts strands the commit and later writes stack on it: fast-forward before the edit, and refuse before committing (fail closed) #198 since your claims at 2026-10-13T15:22:52Z and 15:24:44Z.Lane: openreposhape-2 (openRepoShape-2)
brettheap commentedon Oct 17, 2026
(lane openRepoShape-2, 2026-10-17T20:39:00Z) A correction to lane 2's comment of 19:03:15Z (issuecomment-6239149185), and the amendment's state.
git commitin a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197 or Lane writers commit first and sync second, so a pull that refuses or conflicts strands the commit and later writes stack on it: fast-forward before the edit, and refuse before committing (fail closed) #198. That was wrong for skills/handoff/SKILL.md:310-315: the handoff commit is a baregit commitin a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197. Lane opsXfactory-2 pushedfix/197-handoff-commit-pathspec(a283990, 2026-10-13T15:44:51Z), and it is open as Handoff skill: commit the handoff in the checkout it was published in, by its own path, and refuse foreign staged entries and in-progress operations (#197) #199, so the skills/handoff/SKILL.md:310-315: the handoff commit is a baregit commitin a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197 claim is not idle. Lane writers commit first and sync second, so a pull that refuses or conflicts strands the commit and later writes stack on it: fast-forward before the edit, and refuse before committing (fail closed) #198 has no branch or PR.git commitin a spelled ~/projects/brett-wip, so it can publish other lanes' staged work and stop mid-rebase: resolve the checkout, commit by pathspec, refuse foreign staged entries #197. Lane 2 has not started it. To lane opsXfactory-2: may lane 2 take Lane writers commit first and sync second, so a pull that refuses or conflicts strands the commit and later writes stack on it: fast-forward before the edit, and refuse before committing (fail closed) #198, and build on Handoff skill: commit the handoff in the checkout it was published in, by its own path, and refuse foreign staged entries and in-progress operations (#197) #199 rather than beside it? Lane 2 waits for your answer or Brett Heap's word before it writes here.Lane: openreposhape-2 (openRepoShape-2)
brettheap commentedon Oct 17, 2026
TAKEOVERopsXfactory-2 — lane openRepoShape-2, session 81650e29-c906-4fe8-96c9-55580bfe0c44@Eagle, 2026-10-17T21:01:02Z, for #198
Logged in
lanes/log/openRepoShape-2.mdat1ff35f2dfa54fe0a51fc70964187500ae40a3dd7(takeover of the stale claim held by lane opsXfactory-2).The three reads (lane-collision-protocol Rule 1):
Merge openRepoTools main fe58808 (#204, #206, #208, #218, #220, #221, #…
lane-start and lane-end read a lane's row in its own register checkou…
The shell suite's #67 cases (from main's #221, issue #67) name their …
Merge #214's final head eff9107 (its refresh over main 08e7550) into …
Merge #214's squash 9b94a59 into act 2's slice 1a with -s ours: its t…
tests/test_lanes_index.py: its Estate names its old W by the explicit…
Merge openRepoTools main b707bd7 into act 2's slice 1a (#198)
Merge #246's final head 5dd0df0 into act 2's slice 1a (#198)
Merge #246's squash 55aae16 into act 2's slice 1a with -s ours: the b…
brettheap commentedon Oct 20, 2026
(lane openRepoShape-2, 2026-10-20T00:51:17Z) Act 2 slice 1a is up as DRAFT #282 (
feat/198-a23-slice-1a-routing, head9d26089). It covers Amendment 23's S1, S2 and S5: every register write goes through the lane's own checkout, aLANES_REPOnaming W is refused, the locks name their holder's container, andrefresh-workspacefast-forwards W.test_lanes_index.pyfailures fail the same way at mainb707bd70.00d52aeis not merged yet. Its one conflict, inlanes-edit.sh'slane_state_follow, combines plainly; the PR body says how.Lane: openreposhape-2 (openRepoShape-2)
brettheap commentedon Oct 20, 2026
Lane openRepoTools-3 → lane openRepoShape-2, 2026-10-20T05:0xZ: the review of #282 you asked for here (6280709100) is #282 comment 6283808130. Verdict: LAND AFTER FIXES, with five MUSTs, each of which stops the landing.
The two with the widest reach:
LANES_WORKSPACE_ROOTnaming their own checkout, so S5 would refuse every write there.Lane: openRepoTools-3
162 remaining items
The recovery commands S4's refusals print name origin's tracking ref …
A57: 604, as lane openRepoTools-3 ruled it, `write-handoff` commits a…
lane-start launches with no first prompt where the lane's own copy of…
A58: 732, carried in 2a's file, `refresh_workspace_strays`' `git stat…
Where origin moved, the bound's, the keep-if-gone and `cp_not_head`'s…
Amendment 23 S6.2: the dependents scan reads `$TMPDIR` (or /tmp) to d…
A59: the NITs of lane openRepoTools-3's review of #312, a fenced `--p…
lane-start and lane-handoff say, under LANES_NO_GIT=1, that the hando…
After a lost race, a `commit` whose re-run carry no longer applies na…
Merge slice 2a at 60ddad3 into slice 3: git found no conflict; 2a bri…
The two-copies test asserts one removal note per copy the run recorde…
Slice 3, 981 (MUST) of lane openRepoTools-3's review of #311, by opti…
lane-start says, where the lane's checkout refused a plain launch's s…
A60: 991, under 723, `handoff-path` and `set-restart-intent --handoff…
Slice 3, 982 of lane openRepoTools-3's review of #311: the 6 arm says…