Skip to content

openRepoTools v1.2.0

Latest

Choose a tag to compare

@brettheap brettheap released this 10 Nov 20:56
· 65 commits to main since this release
ecd1bce

openRepoTools v1.2.0

v1.2.0 makes every register write fetch before it edits and confirm on origin after it pushes, writes a lane's handoff by that same act, and stops the lane tools from losing a lane they could not read whole. It is openRepoTools as it stands at ecd1bce7b1079ef81215d6166b6db9ee7d481adf in opensoft/openRepoTools-code, tagged on 2026-11-10. 24 pull requests landed on the code leg's main between v1.1.0's commit, 2a057a6d, and this release's: #2 to #25, the last of which made that main ecd1bce. 11 more landed on the spec leg's main, in opensoft/openRepoTools-spec, after v1.1.0's notes: opensoft/openRepoTools-spec#6 to opensoft/openRepoTools-spec#18, all but opensoft/openRepoTools-spec#14 and opensoft/openRepoTools-spec#17, which are open.

v1.2.0 is the second version on PyPI, after 1.1.0. It is published from opensoft/openRepoTools-code, as 1.1.0 was, and its tag is made there. Feature 007, the SQL register (opensoft/openRepoTools#165), is not part of it: on Brett Heap's word of 2026-11-10 it moves to the v1.3.0 milestone, and no part of it ships here.

What 1.2.0 adds

  • Every register write fetches first, and says pushed only once origin has it (opensoft/openRepoTools#198; #17 with opensoft/openRepoTools-spec#15). This is act 2 of lane-collision-protocol Amendment 23. Each lanes-edit.sh writer, routed or not, now fetches and fast-forwards its checkout under the checkout's lock before it reads the file it edits. Where the checkout cannot be brought to origin's tip, it refuses with exit 2, nothing written, and names the checkout and its recovery: a fetch that failed, LANES_NO_FETCH=1 among them; an operation in progress or unmerged entries; HEAD off the branch; staged entries that are not the write's own; unpushed commits; a diverged branch; a pending change to a path the write commits; or a fast-forward git refused. The write then commits by pathspec, pushes with no pull, and reads origin back. pushed origin/<branch> (attempt N) and exit 0 now mean that origin holds the commit, and a push that went, or timed out, and cannot be confirmed is exit 3, pushed, not confirmed: <sha>, never success. A push refused because origin moved replays the one edit on origin's new tip, with the verb's own checks run again, a claim's rescan among them, up to six attempts in all. At that bound, or where a push is refused with origin unmoved, the commit is kept on HEAD and named, with exit 6, and every later write from that checkout refuses until the lane publishes or archives it. lanes-edit.sh commit with nothing staged is the publish act: it sends the commits origin lacks by a fast-forward push only, and refuses (2) a stack that origin has moved past, naming the archive and the reset --keep that settle it.
  • A lane's handoff is written by one verb, under the lock (opensoft/openRepoTools#198; #18 with opensoft/openRepoTools-spec#16, #20 and #19). lanes-edit.sh write-handoff <lane> writes a lane's handoff in the checkout its writes go through, as a Rule 3 stamp (--stamp), a new top block (--prepend) or a whole file (--whole), under that checkout's lock and with the fetch, the publish, the read-back and the replay that every other write has. With --prepend it proves that the block and the old file both survive before it replaces the file. lanes-edit.sh handoff-path <lane> [--create] says where that file is. lane-start writes its Rule 3 stamp, and lane-handoff its PAUSED block, each with one write-handoff call, and the handoff skill writes its new top block with one write-handoff --prepend call, so none of the three commits, pulls or pushes on its own any more. After its first write, lane-start refuses a launch only at a restart's own fences; where the stamp could not be written it says so and prints the write-handoff --stamp call that writes it. A launch reads only the file handoff-path names, and where that file is not there it goes on with no first prompt and names the file. Where lane-handoff or the skill could not write the top block, the block is kept and printed with the one write-handoff --prepend call that puts it in. Under LANES_NO_GIT=1, lane-start and lane-handoff say that the handoff is written and nothing is committed or pushed.
  • Each lane can write through its own clone of the register, by opt-in (opensoft/openRepoTools#198; #16 and #20). W is the checkout of the register that workspace.yaml's path: names. With LANES_ROUTE_WRITES=1 and no LANES_REPO, every write goes through the lane's own clone, <W>-lanes/<lane>/, made on first use, while reads still read W. W then takes no commit: a LANES_REPO that names W refuses every write (2), and W moves only by lanes-edit.sh refresh-workspace, a fast-forward, which lane-start runs before and after its writes. lanes-edit.sh lane-checkout <lane> [--create] names a lane's clone, or makes it. lane-worktrees' report lists <W>-lanes/<lane> under "Register checkouts (Amendment 23 S1)", with what each holds that origin lacks, and never under FOREIGN. Without LANES_ROUTE_WRITES=1, which is how this release ships, every write goes through LANES_REPO, W by default, and every lock is that checkout's, as in 1.1.0.
  • lane-end --sweep-checkouts removes the clones nothing holds (opensoft/openRepoTools#198; #21). It lists the clones under <W>-lanes/ that CLOSED lanes left, and the clones of the register beside W, and it is a dry run by default: exit 3 where something is removable, and 0 where nothing is. With --yes, run as the lane LANES_LANE names, it removes only those nothing holds, each decided again under its own lock after a fetch. W, a live lane's clone, the runner's own, the one a caller's LANES_REPO names and a symbolic link are never candidates. Work origin lacks, uncommitted or untracked files, a stash, an operation in progress, a repository that borrows the clone's objects, a linked worktree, a process working inside it or naming it in LANES_REPO, and a file system mounted in it each hold a clone. What it removed is recorded in one NOTED line in the runner's own log, and exit 1 is a read that failed, or a removal that did not finish or was not recorded. It refuses (2), the dry run too, from a window whose LANES_WORKSPACE_ROOT names a lane's own checkout, and --yes refuses (2) while that variable names any directory but workspace.yaml's path:: run it with the variable unset.
  • The lane tools no longer lose a lane they could not read whole (the five issues of the v1.2.0 milestone, carried to the legs as part of opensoft/openRepoTools#186):
    • A lane whose recorded directory holds a space is no longer left out of worktrees --all, of worktrees <lane> and of the inventory index: worktree_rows hands the directory on quoted where it holds a space, as lane-start writes it (opensoft/openRepoTools#295, #3).
    • A lane whose recorded directory holds a TAB or a backslash is listed, indexed and reached as recorded. The listing's directory column is escaped as lane-worktrees escapes a porcelain field, and lanes, lane and worktrees decode it. Every read of a stored sub-field gives back what was written, because payload_subfield no longer passes it through awk -v. Such a lane's lifecycle and trees are now looked for beside its checkout, and records the old read filed under $PROJECTS_ROOT/.lane-state or a sibling directory stay there, unread, with no migration. The binding and pause checks judge the text that is written and not its awk -v expansion, so host a\101b is no longer passed as aAb, and request-handoff refuses a requesting host or container outside letters, digits, ., _, - and :, on the request and --force paths and on either one's --dry-run, with exit 2 and "Nothing was written" (opensoft/openRepoTools#301, #4 with opensoft/openRepoTools-spec#7).
    • A lane's control root, or a directory above it, that this account cannot search is read as not read, not as no trees: lane-state and lane-trees exit 10, not 8, and say why, worktrees names the lane UNREAD so that lanes-index sync keeps its rows, lane-reconcile is indeterminate, and a write under such a root fails instead of landing under $PROJECTS_ROOT (opensoft/openRepoTools#291, #5 with opensoft/openRepoTools-spec#8).
    • A tree record that names no absolute path no longer swaps its tree's row in the index for one keyed by the record's id: worktrees names the lane UNREAD, so lanes-index sync and reconcile --source inventory keep the lane's rows and say why, and one such record holds every row of its lane until it is put right (opensoft/openRepoTools#302, #6 with opensoft/openRepoTools-spec#9).
    • A named worktrees <lane> read says UNREAD, with exit 0, where the lane's log or the register could not be read, where it used to say none (8). The lanes listing says what it could not read and exits 1 where it names no lane for that reason, a tree's writer is unknown and not 0 where the lane's binding was not read, and lane-state exits 10, not 8, over a log it could not read, so lanes-index holds that lane's rows (opensoft/openRepoTools#290, #7 with opensoft/openRepoTools-spec#10). A known gap remains (opensoft/openRepoTools#303): most other reads of a lane's log still cannot tell a failed read from a lane with no log.

What else changed since v1.1.0

  • The openRepoShape pin moved. This release pins openRepoShape at v1.1.1, 5017830 on its main, where 1.1.0 pinned v1.1.0, 190133d. Four of the eight files the suite mounts from it changed, and no test expectation moved (#15).
  • The lane tools' other fixes:
    • lanes-edit.sh claim --force posts a takeover's GitHub comment that opens as Rule 1 spells it, TAKEOVER, a dash and then the taker's lane, where it ran TAKEOVER into the name of the lane taken over from, so gh_reads, which looks for that opening, now finds a takeover comment (opensoft/openRepoTools#346, #2).
    • duplicate-holder no longer reports the lane's own process tree as a duplicate pair where the lane's live pid is a wrapper's child, so lane-end --retire on that wrapper exits 8 and ends nothing, where it said to retire "the OTHER pid". Where a second holder runs under the same wrapper, the read exits 1 naming the wrapper, the lane's own pid and that holder, and lane-end relays its words. lane-end --retire passes over a helper too old for duplicate-holder and goes on to the session records, and where it skipped a duplicate-holder or transcript-holders read it no longer says that nothing of any kind is live: it dies 1, saying that a read that did not run is not "nothing". Both of these paths are defensive, since no lanes-edit.sh that has shipped lacks either verb and passes lane-end's seam check. The suite's fake pgrep and ps can now fail, so the branches that read a failing pgrep or ps as a read that failed are tested (opensoft/openRepoTools#109 to opensoft/openRepoTools#112, #25).
  • The release workflow and its checks, follow-ups to opensoft/openRepoTools#228:
    • Both wheel checks hold a payload file to the commit's whole mode, 100755 or 100644. release.yml's wheel and index checks refuse with a named ::error:: line instead of a traceback, a member stored as LZMA with corrupt data included, and the macOS rerun places the sixteen commands under bash 3.2 in a bin directory of its own, then runs --install there a second time (opensoft/openRepoTools#278, #12 with opensoft/openRepoTools-spec#11).
    • Six checks of tests.yml's package job can no longer be removed with the suite green: tests/test_tests_workflow.py runs its wheel check on four more wheels and its step 10 whole, and holds the pipx lock's tomli entry equal to the build lock's (opensoft/openRepoTools#275, #13 with opensoft/openRepoTools-spec#12).
    • release.yml's RELEASING header says that no job reads more of Brett Heap's release acts than the required reviewer and the deployment limit, and that the build checks only that a required reviewer is named; it names the tag ruleset by its rule and by this repository's own name, and tests/test_release_workflow.py pins those sentences. The spec and the OpenSpec proposal say that only the admin role creates, moves or deletes a v* tag (opensoft/openRepoTools#279, #14 with opensoft/openRepoTools-spec#13).
    • release.yml's wheel check refuses, with a named ::error:: line, a directory entry the wheel may not hold, a wheel that declares more than 256 MiB uncompressed and a member stored in any method but STORED or DEFLATED; it hashes each member in 1 MiB chunks as it reads it; and its four index checks end a too-deep JSON answer with their named refusal instead of a traceback. A wheel built by the pinned hatchling passes (items 1 to 3 of opensoft/openRepoTools#352, #23).
    • tests/test_release_workflow.py runs the wheel check's whole step on a rehearsal's branch and on the version's tag, so a TAG taken from the ref's name, or a REPOSITORY hard-coded to the code leg, fails the suite (opensoft/openRepoTools#285, #8).
    • It makes a served-digest read fail with a URLError that has no HTTP status, and another time out, so release.yml's retry cannot lose either with the suite green (opensoft/openRepoTools#286, #9).
    • It runs the ref step on a tag in a checkout that holds four commits and requires it to leave them whole, so a --depth on its fetch cannot pass the suite (opensoft/openRepoTools#287, #10).
    • It runs the stamp writer and the steps after it through the wheel check, so a step that rewrites the stamp is refused by the wheel check and not only counted by the step-order pin (opensoft/openRepoTools#288, #11).
  • CI runs the suite as shards (opensoft/openRepoTools#77, #22 with opensoft/openRepoTools-spec#18). tests and tests-no-submodule are now gates over matrices of shard jobs, four pytest shards and a shell shard for tests and three and a shell shard for tests-no-submodule, each shard timing out at 60 minutes. The four required check names stay; a gate is never skipped, and it fails unless every shard passed and the shards' test ids equal the full collection, id for id. Runs on main since it landed take 28 to 38 minutes, the release commit's own 38, against 76 to 90 for the runs before it on 2026-11-09. The 006 workflows contract describes tests.yml as it now is.
  • The test suite: the inventory tests' norm takes the sandbox root out before the tree-id and pid rules, so a run root with a c<digits>- segment no longer fails two tests (opensoft/openRepoTools#334, #24).
  • The triad migration's records: rehearsal D's record, the Gate B walk at rehearsal D and its plan, Plan-D, are carried into the spec leg as their final bytes, with Plan-D beside run C's plan, as part of opensoft/openRepoTools#186 (opensoft/openRepoTools-spec#6).

Install and upgrade

  • From PyPI: https://pypi.org/project/openrepotools/1.2.0/ is this release's page. pipx install openrepotools, or pipx install openrepotools==1.2.0 for this release, then openRepoTools --install, as for 1.1.0, whose notes say where pipx comes from and how to move to it from the one-line install. openRepoTools --version from the package prints five lines:

    openRepoTools 1.2.0 (opensoft/openRepoTools-code @ ecd1bce7b1079ef81215d6166b6db9ee7d481adf)
      version   1.2.0
      tag       v1.2.0
      commit    ecd1bce7b1079ef81215d6166b6db9ee7d481adf
      digest    f44ef8716263a7d0f8ea3e2f200eca398b178ee6c362e00ef41ed156c1d4280f (sorted-ls-tree-r-v1)
    
  • Upgrade from 1.1.0: pipx upgrade openrepotools, then openRepoTools --install again. Until that second command, the placed files, skills, command files and hook entries stay 1.1.0's. Run both: this release's handoff skill calls lanes-edit.sh handoff-path and write-handoff, which 1.1.0's lanes-edit.sh does not have. openRepoTools itself is unchanged since 1.1.0, so --install, its count line, its receipt and the hook pair behave as they did.

  • A writer no longer pulls. What a script or a person that calls lanes-edit.sh may meet (#17):

    • LANES_NO_FETCH=1 makes every writer refuse (2), with nothing written, because a write must fetch first. Unset it for writes.
    • Exit 3 has one meaning now, pushed, not confirmed: <sha>. Its old causes, a rebase conflict and six attempts blocked by a peer's uncommitted file, went with the pull, and a push that timed out is 3 only where the read-back cannot confirm it. A write that runs out of attempts is exit 6, its commit kept on HEAD and named, so 6 no longer means only that git add, commit or push failed. Exit 0 for a write means that origin holds the commit.
    • Unpublished commits in a checkout make every later write there refuse (2) until lanes-edit.sh commit, with nothing staged, publishes them or they are archived. A hand edit of the register is published by its own act, LANES_LANE=<lane> lanes-edit.sh commit "<what the edit is>".
    • --sweep is withdrawn: every verb but the two hooks refuses it (2), and --no-sweep is accepted and changes nothing.
    • No refusal tells you to git pull --rebase first any more.
  • Exit codes that changed in the lane tools:

    • lane-state and lane-trees: 10, not 8, where the control root or a directory above it cannot be searched, and lane-state also where the lane's log could not be read (opensoft/openRepoTools#291, opensoft/openRepoTools#290).
    • lanes-edit.sh worktrees <lane>: 0 with an UNREAD line, not 8, where the lane's log or the register could not be read (opensoft/openRepoTools#290).
    • lanes-edit.sh lanes: 1, not 8, where it names no lane because the register, the archive or the list of the logs could not be read, and the lanes command refuses on it (opensoft/openRepoTools#290).
    • lanes-edit.sh request-handoff: 2, with nothing written, for a requesting host or container outside its alphabet (opensoft/openRepoTools#301).
    • lane-end --retire: 8, not 2, where the only pair duplicate-holder found was the lane's own process under its wrapper, and 1, with the helper's words, where a second holder runs under that wrapper (opensoft/openRepoTools#111).
  • Write routing stays off in 1.2.0. Leave LANES_ROUTE_WRITES unset: turning it on by default is opensoft/openRepoTools#356. Do not set LANES_ROUTE_WRITES=1 while lane windows export LANES_WORKSPACE_ROOT naming the lane's own checkout, or every write refuses with 2. Keep the windows' LANES_REPO and LANES_WORKSPACE_ROOT exports as they are: with routing off, a write goes through the checkout LANES_REPO names. Install the helper, lanes-edit.sh, and the handoff skill together; openRepoTools --install places both. A workstation whose shared checkout, W, holds commits origin lacks repairs it first, by hand, as Amendment 23's S6.5 sets out, because a writer with LANES_REPO unset writes through W, and over those commits it refuses with 2 and writes nothing.

  • The per-lane clones are opt-in. LANES_ROUTE_WRITES=1 turns them on. Each writing lane then has its own clone of the register, about 0.4 GB each as #17 gives it, and lane-end --sweep-checkouts removes the ones CLOSED lanes leave. Under it, a 1.1.0 helper still running beside this release's takes W's mutex for the lifecycle, restart and tree acts where this one takes the workstation's lock, so the two do not serialise those acts with each other; the fence in each act stays the check (#16).

  • One install route or the other, never both, as for 1.1.0: until opensoft/openBenches#165 lets workBenches' scripts/setup-estate-commands.sh accept pipx's link, a workstation uses pipx's route or workBenches' ./setup.sh, as 1.1.0's notes describe. State of opensoft/openBenches#165 at the tag: open, and not landed, as read on 2026-11-10.

  • The one-line install and a checkout work as before, and their --version still prints a repository and ref and no release. They take the code leg at the commit the assembly root pins. Since opensoft/openRepoTools#357 that is ecd1bce, the commit this release was built from, or a later one, so they carry this release.

  • Windows is reached through WSL2, as for every bash command here. The package has no native Windows command, and it is a wheel only, with no source distribution.

Tagged on Brett Heap's word (2026-11-10T16:51:42Z, "yes, tag v1.2.0 at ecd1bce and dispatch it").

Full list

These notes are docs/release-notes/v1.2.0.md in opensoft/openRepoTools-spec.