Skip to content

lane-handoff reads the lane lifecycle whole and parses it afterwards, so an awk that exits early can no longer turn lanes-edit.sh's SIGPIPE into a lifecycle that could not be read (fixes #280) - #283

Merged
brettheap merged 2 commits into
mainfrom
fix/280-lane-state-sigpipe
Oct 20, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

This fixes the CI flake in #280. It must land before the triad rehearsal's freeze (#186), because every PR's tests job runs the T018 handoff test that the flake turned red at random.

What it fixes

  • Before: lifecycle_begin (lane-handoff:1490-1491 at 21e0829) piped lanes-edit.sh lane-state "$lane" into awk -F"\t" '$1 == "state" { print $2; exit }', under set -uo pipefail.
    • An awk that returns at the first record closes the pipe while lanes-edit.sh is still writing. gawk, CI's awk, does this.
    • The next write gets SIGPIPE, so the substitution's status is 141. case "$lcb_rc" in 0 | 8) reads that as a failed read.
    • The handoff then printed "the lane lifecycle could not be read (… exited 141)", and recorded no RUNNING -> SWAPPING -> SWAPPED transition, no fence and no inventory.
    • This failed safe, but it was wrong, and whether it happened depended on timing alone.
  • Now: the read takes lane-state's whole output into a variable first, so the status is lane-state's own.
    • The variable is then parsed with the same filter, through printf '%s\n' "$lcb_raw" | awk …, the form ri_load uses.
    • The second read, of the operation of a lane still SWAPPING (:1505-1506), is made the same way for uniformity. Its status was never read, so it could not fail.
  • Unchanged:
    • a status of 0 or 8 is still a read, and anything else gives the same note, byte for byte;
    • [ -n "$lcb_now" ] || lcb_now=none stays;
    • the number of lane-state calls is the same;
    • no output the shell suite can see changes.

The audit

The branch: fix/280-lane-state-sigpipe is two commits on main 21e0829. It changes lane-handoff (+17 −5) and tests/test_triad_lane_tooling_handoff.py (+134 −2).

  • fc061fe: the fix and two tests.
  • ae752a7: the review's SHOULD and NITs 2 to 4. The test's fake lane-state pads the pipe instead of sleeping, and the awk shim goes.

Tests. test_280_a_lifecycle_read_whose_producer_writes_after_the_filter_exits_is_still_a_read and test_280_a_swapping_lane_read_the_same_way_still_names_the_operation_it_supersedes sit beside T018's tests.

  • The fake: a wrapper lanes-edit.sh execs the bench's real one for every verb except lane-state. For that, it prints the state line, then 1.5 MiB of padding, then the rest of the record.
  • Why it always races: a pipe holds 64 KiB, so an awk that exits early always leaves the fake still writing. The old read gets 141 every time, whatever the timing or the awk. Plain mawk reads ahead at most about 320 KiB.
  • What they check:
    • the first test asserts that premise, that the old read shape gives RUNNING 141, and then that the handoff records RUNNING -> SWAPPING -> SWAPPED;
    • the second pins the note's content and the first read on a lane that is SWAPPING.
  • Results: on 21e0829's lane-handoff, both fail with "exited 141". On this head, both pass.

Review. Lane 3's independent review gave LAND on fc061fe, with one SHOULD and three NITs.

  • The SHOULD: the original 0.3 s pause made the premise itself a race.

  • All four were taken in ae752a7, and the review gave LAND on ae752a7 with no new findings.

  • The old and new lifecycle_begin gave byte-identical results in 27 of 28 cases. The 28th is a NUL byte, which lane-state cannot emit.

  • Mutants: each of these fails both tests:

    • the :1490 fix reverted;
    • the old pipeline put back;
    • an awk that exits early but passes its lines through.

    Reverting the :1505 fix alone passes, because no test can see that site.

Runs

Landing. This lands after lane 3's queue (#262, #267 to #271), on Brett Heap's word of 2026-10-18T20:37:07Z: "use this as approval to merge when green for all merges thru v1.1.0".

Fixes #280.

Lane: openRepoTools-3

🤖 Generated with Claude Code

Summary by Sourcery

Make lane-handoff reliably read lane lifecycles without allowing early parsing termination to corrupt the command status.

Bug Fixes:

  • Prevent lane-handoff from misinterpreting producer SIGPIPE failures as unreadable lane lifecycles.
  • Preserve lifecycle transitions and recovery handling when lane-state output is consumed by an early-exiting parser.

Enhancements:

  • Read complete lane-state output before parsing it, applying the same behavior consistently to swapping-lane operation reads.

Tests:

  • Add deterministic regression coverage for early consumer termination during lifecycle reads, including lanes already in the SWAPPING state.

Brett Heap and others added 2 commits October 19, 2026 23:07
… so a filter that exits early can no longer turn the producer's SIGPIPE into a lifecycle that could not be read (#280)

lane-handoff read `lanes-edit.sh lane-state` as `lane-state | awk '… { print
$2; exit }'` under `set -o pipefail`. That awk exits at the line it wants, so a
lane-state that wrote once more after it - the builtin printf hands a
multi-line snapshot to the pipe in pieces - died of SIGPIPE, the substitution's
status was 141, and the case on it took that for a read that failed: "the lane
lifecycle could not be read", no fence, no inventory and no RUNNING ->
SWAPPING -> SWAPPED, on timing alone (CI run 39322387035). The reader had
stopped listening; the read had not failed.

Both lifecycle reads - the state, and the operation of a lane still SWAPPING -
now capture lane-state's whole output first, take the status from that command
alone, and parse the variable with the same awk filter. A lane-state that
exits 0 gives the values the old pipeline gave whenever it did not race, 8 and
every other status keep their meanings, and the note texts are unchanged.

The other 32 early-exit pipelines in lane-handoff (30 `awk … exit`, 2 `head -n
1`) were audited and left: none has a command producer that can write after
its consumer stops and a status that is read (a printf of one variable, one
short line, a status never read, or a consumer that reads to the end).

Two tests next to T018's hold the race every time: a lane-state that prints
its state line, waits 0.3 s and prints the rest, in front of the old read
shape (premise: `RUNNING 141`) and in front of the handoff. mawk, this
workstation's awk, hands its first record over only at EOF or after hundreds of
KB, so the old pipeline cannot race on a snapshot this small there; the command
under test gets an awk that does not wait (`mawk -W interactive`, or the
system's own where it is not mawk).

Lane: openRepoTools-3
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ping, so the old read's 141 no longer depends on timing, and the awk shim goes (#280)

The review of fc061fe came back LAND with one SHOULD and three NITs; this
takes them all, in the test file only. fc061fe is not touched.

SHOULD - the premise was a race of its own. The fake lane-state wrote its
state line, slept 0.3 s and wrote the rest, so an awk that stalled for longer
than the pause read it all, the old read gave `RUNNING 0`, and the premise
assertion failed on correct code. It now writes the state line, 1.5 MiB of
padding and then the rest of the record. The padding's key, `pad`, is no
field lane-state prints, so no filter in lane-handoff can take it for one. A
pipe holds 64 KiB, so that producer is still writing when a reader that stops
at the first line has closed: its next write gets SIGPIPE, the wrapper dies of
it, and the old read gives 141 whatever the timing and whatever the awk. Plain
mawk is included - it reads ahead until its buffer is full (the end of input
at 255 KiB or less, an early stop at 320 KiB or more), and the padding is far
above that. The quick-awk shim, its PATH entry and the wrapper's PATH reset
are gone, and the premise assertion (the old read shape gives `RUNNING 141`)
stays and is now deterministic.

NIT 2 - the tests no longer set LANES_NO_FETCH=1 for the whole handoff;
lane-handoff sets it on its own reads. The premise script, which reproduces
the old line, carries it as that line did.

NIT 3 - the second test's docstring says what it pins: the note's content and
the first read on a SWAPPING lane. It cannot pin the second read's own fix,
whose status was never read, so reverting that site alone still passes.

NIT 4 - quick_awk's docstring went with the shim.

Lane: openRepoTools-3
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@brettheap brettheap added this to the v1.1.0 milestone Oct 20, 2026
@sourcery-ai

sourcery-ai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Fixes a CI flake in lane-handoff by buffering each lane-state response before parsing, ensuring producer status is not replaced by SIGPIPE from an early-exiting awk; deterministic tests reproduce the failure and verify lifecycle transitions and SWAPPING diagnostics.

File-Level Changes

Change Details Files
Make lane lifecycle reads immune to producer SIGPIPE caused by early-exiting parsers.
  • Capture the complete lane-state output before parsing it with awk, preserving lane-state’s exit status.
  • Apply the same whole-output read pattern to the SWAPPING operation lookup.
  • Preserve existing accepted statuses, diagnostics, call counts, and observable shell output.
lane-handoff
Add deterministic regression coverage for lifecycle reads whose producer outlives an early reader.
  • Extend the handoff helper to pass a custom environment.
  • Add a lanes-edit.sh wrapper that emits substantial padding after the state line to force SIGPIPE with the old pipeline.
  • Verify the old read returns 141 and the fixed handoff records lifecycle transitions and inventory.
  • Verify SWAPPING handoffs retain the superseded operation in their diagnostic.
tests/test_triad_lane_tooling_handoff.py

Assessment against linked issues

Issue Objective Addressed Explanation
#280 Prevent an early-exiting awk from causing the primary lane lifecycle read to report a false failure under pipefail, so the handoff records the lifecycle transition. ✅
#280 Apply the read-whole-output-before-parsing approach to the operation read for lanes already in SWAPPING, while preserving its diagnostic behavior. ✅
#280 Audit other early-stopping consumers for vulnerable producer pipelines and fix any cases where early closure can affect a status the code uses. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@brettheap brettheap changed the title lane-handoff reads the lane lifecycle whole and parses it afterwards, so an awk that exits early can no longer turn lanes-edit.sh SIGPIPE into a lifecycle that could not be read (fixes #280) lane-handoff reads the lane lifecycle whole and parses it afterwards, so an awk that exits early can no longer turn lanes-edit.sh's SIGPIPE into a lifecycle that could not be read (fixes #280) Oct 20, 2026
@brettheap

Copy link
Copy Markdown
Contributor Author

Lane openRepoTools-3, 2026-10-20T01:2xZ: the independent review of this PR is LAND, at ae752a7.

Round 1, at fc061fe: LAND, with one SHOULD and three NITs.

  • The fix is correct. lane-handoff:1489-1518:
    • takes the read's status from lane-state alone, and 0 or 8 is still a read;
    • leaves the note texts untouched, and keeps [ -n "$lcb_now" ] || lcb_now=none;
    • sets every new variable before use, and uses nothing past bash 3.2.
  • Old and new lifecycle_begin against a fake $LANES_EDIT: byte-identical in 27 of 28 cases. The cases covered lane-state exits 0, 8, 10, 1, 2 and 141; whole RUNNING and SWAPPING records; empty output; CRLF; UNKNOWN-SCHEMA; a 257 KB record; set-lane-state exits 0, 7, 1 and 3; and a second read that fails or differs.
    • The values, the stderr, and every call's arguments, including --expect, all matched.
    • The 28th case is a NUL byte, which lane-state cannot emit.
  • The audit holds. No pipeline left in lane-handoff combines a command as the producer, a reader that stops early, and a status that is used.
  • shellcheck 0.9.0 gives the same 56 findings, per code, on the old and the new file, and none on the changed lines.
  • SHOULD: the test's premise rested on a 0.3 s pause, so it was itself a race. Measured, it was rare: 300/300 at every pause from 0.3 ms to 0.3 s, under load. But it was not certain. The fix proposed and tested was padding past the pipe buffer.
  • NITs: LANES_NO_FETCH=1 set for the whole handoff without a reason; test 2's docstring overclaimed; and the shim's docstring numbers were wrong.

Round 2, at ae752a7: LAND, with no new findings. It takes the SHOULD and all three NITs.

  • Padding instead of a pause: the fake lane-state prints 1,572,864 bytes of padding between the state line and the rest.

  • Why timing no longer matters: a pipe holds 65,536 bytes. When awk closes, the producer has written at most what awk read plus 64 KiB, so its next write gets SIGPIPE whatever the scheduling.

  • The margin: the greediest reader, plain mawk, stops after about 288 to 320 KiB, so the padding is about 4.8 times that.

  • Measured with the commit's own wrapper:

    Reader RUNNING 141
    plain mawk 200 / 200
    an awk that returns at the first record (as gawk does) 200 / 200
    an awk that starts 2 s late 10 / 10
  • Mutants, -k test_280:

    Mutant Result
    none 2 passed
    M1, the :1490 fix reverted 2 failed at :887 and :915, "exited 141"
    M3a, the old early-exit awk put back 2 failed, the same
    M3b, an early-exit awk that passes lines through 2 failed, the same
    M2, the :1505 fix reverted alone 2 passed: no test can see that site, as test 2's docstring now says

Why CI hit the race and Eagle did not.

  • CI: the failed job ran on image ubuntu-24.04 20261012.334.1, which is Ubuntu 24.04.5. Its manifest lists gawk, which registers as /usr/bin/awk above mawk, and gawk returns at the first record.
  • Eagle: the default awk is mawk 1.3.4 20240123, which reads a small input to the end.

Follow-up: the same shape in lane-start is #281, filed for v1.2.0.

Lane: openRepoTools-3

@sonarqubecloud

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. Capturing the full lane-state output avoids treating the reader's SIGPIPE as a failed read, allowing the handoff to write lifecycle transitions into the lane record. If the read or parse is wrong, those persisted transitions may need to be reset or repaired; reverting restores the old read behavior but does not undo records already written.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@brettheap
brettheap merged commit 677acc0 into main Oct 20, 2026
5 checks passed
@brettheap
brettheap deleted the fix/280-lane-state-sigpipe branch October 20, 2026 03:35
brettheap added a commit that referenced this pull request Oct 20, 2026
… one, and a leg directory this account cannot search, or a dependency nested in one, is unreadable and never absent (fixes #255) (#271)

A follow-up to #246 (T018 of feature 004, the triad lane tooling) that must land **before the triad cutover (#186)**. It fixes the two sections of #255.

**The handoff names every inventory record that is not a whole schema-1 one, and a leg directory this account cannot search is unreadable, never `absent`.** Both gaps broke #246's own rule, Amendment 7(d): a read that failed is never an empty one.

These readers run only in an assembly, a checkout whose `project.yaml` declares a leg, so main is not exposed before the cutover.

**What it fixes**
- **Section 1, the handoff's tree inventory** (`lane-handoff`, `paired_inv` and `pi_bad`):
  - **Before:** a `lane-trees` row counted as unreadable only where its path was empty. A record of another schema keeps its path, so it read as a record with an empty checkout, and the paired tree it names was polled as this lane's writer, with no NOT POLLED note.
  - **Now:** the row's schema, field 13, decides. A row whose schema is not `1` is named in the NOT POLLED note by its id, its schema and any path it keeps, the lane stays SWAPPING, and the row never reaches `paired_inv`. The `lane-trees` row format is untouched.
- **Section 2, a leg directory with no search permission:**
  - **Before:** its `.git` could not be tested, and each reader's `.git` test failed there exactly as for a leg that is not checked out. The sweep listed it `absent` and exited 0. `lane-reconcile` gave no TREE row. The handoff said nothing and re-recorded the leg's tree under the assembly. `lane-end` ENDED the lane over the lane's unpushed commit in the leg.
  - **Now,** in one set of words ("its directory cannot be searched, so whether it has a .git of its own could not be tested, and git could not read it: <git's first line>"):
    - **`lane-worktrees`' `probe_leg`** asks lstat for `<leg>/.git`. Only ENOENT or ENOTDIR is a leg with no `.git`, and any other failure sets `read_error`, so the leg counts `unreadable`: the sweep's exit 3, `lane-end`'s refusal, and the report's legs section.
    - **`lane-handoff` and `lanes-edit.sh`'s `lane_reconcile`** take their unreadable-leg branch where the `.git` test fails and the leg directory fails `-x`. The handoff gets an UNREADABLE LEG line, a count and a withheld tree. The reconciliation gets a TREE row `unreadable` that requires recovery.
    - **A leg directory that is not there at all** is still `absent`.

**What the fold-in adds.** Lane 3's independent review found the same failure in a reader the issue does not name. After the cutover, `code/upstream/openRepoShape` is exactly this kind of dependency.
- **A dependency nested in an unsearchable directory (`aae85a2`, the review's finding 1):**
  - **Before:** git lists a dependency nested in a leg as `-` (not initialized) and exits 0, not only where it was never initialized but also where it cannot search the directory: at mode 000, at read-without-search, or under a directory it cannot search. `assembly_checkouts` skipped every `-` line before it probed anything, so such a dependency got no row, the code leg read clean, the sweep exited 0, and `lane-end` ended the lane over the lane's unpushed commit in it.
  - **Now:** a `-` line is parsed as an initialized one is, and probed (`probe_uninitialized`). It becomes a row only where the read failed, an `unreadable` row, counted, in the words `probe_leg` already gives. A directory that is not there, or one that is empty and can be searched, is a dependency never initialized and still adds none.
- **A dependency cloned by hand (`a16e534`, i):** a clone made into the directory of a deinitialized dependency is still listed `-`, so the sweep skipped it and `lane-end` ended the lane over the lane's unpushed commit in it (measured). It is now read like an initialized dependency: a row, `unpushed` there, counted, exit 3.
- **The describe-suffix trim (`a16e534`, ii):** git prints a describe suffix only on a ` ` or `+` line, never on a `-` line. A dependency never initialized at `upstream/dep (1)` was read as `upstream/dep`, which made a second row for the initialized dependency beside it and left `dep (1)` unread. The trim now applies to the initialized branch only.
- **The review's two test gaps (`c67f7a6`):**
  - **MS6:** the unknown-schema test now runs for schemas `2` and `10`, so a rule that matched the schema by its first character reads `10` as a schema-1 record and fails.
  - **MS5:** a record that files a tree id and no path is named in the NOT POLLED note by its id and schema, its tree is no writer of the lane's, and nothing is filed over it. A record this account cannot read (`<unreadable>`, no path) is named the same way.
- **The docs (`c67f7a6`, `a16e534`):** `docs/README-lanes.md` says what the sweep does with a dependency git lists as not initialized, and #255's own added text is reflowed where its insertion left ragged lines.

**The branch.** `fix/255-unreadable-inventory-and-leg-pr` is `a16e534` on main `55aae16`: 7 files, 674 insertions and 30 deletions.
- **Lane openRepoTools-2's two commits, cherry-picked:**
  - `13397aa` (from `ba13d21`): the two fixes, in `lane-handoff`, `lane-worktrees` and `lanes-edit.sh`, with four tests;
  - `e678c61` (from `523b381`): `docs/README-lanes.md`'s T018 section, `docs/triad-lane-tooling-analysis.md` section 7 item 11 naming opensoft/openBenches#166, and `lane-handoff`'s T018 section heading, now `3b`.
- **Lane 3's three fold-in commits:** `aae85a2`, `c67f7a6` and `a16e534`.
- **Why a cherry-pick:** #246 landed as the squash `55aae16`, so a merge of main into lane 2's branch conflicts in all seven files. The PR is the two commits cherry-picked onto main, with no force-push anywhere. Their diff equals `git diff 5dd0df0 523b381` (the same patch-id), and their tree at `e678c61` is `1a46c72`, the tree the reviewer computed.

**Tests.** There are 11 `test_255_` functions: lane 2's four, and the fold-in's seven. Under **Runs**, six of the seven fail under a mutant of the code they hold. The seventh, the unreadable record, needs no #252.
- **Lane 2's four,** each failing on `5dd0df0`'s tools and passing here:
  - `tests/test_triad_lane_tooling.py`: `test_255_a_leg_directory_that_cannot_be_searched_is_unreadable_and_holds_lane_end`;
  - `tests/test_triad_lane_tooling_handoff.py`: `test_255_a_leg_directory_that_cannot_be_searched_is_unreadable_in_the_handoff_and_reconcile`, `test_255_an_unknown_schema_record_is_named_in_the_handoffs_not_polled_note` and `test_255_a_malformed_record_is_named_in_the_handoffs_not_polled_note`. The `<malformed>` row comes from a fake `lanes-edit.sh`, so this test does not wait for #252.
- **The fold-in's seven:**
  - `tests/test_triad_lane_tooling.py`: a nested dependency's directory at mode 000 and at 0600, and beneath a directory that cannot be searched, each holding the sweep and `lane-end`; a dependency never initialized, which adds no row; a dependency cloned by hand, read as `unpushed`; and a dependency at `upstream/dep (1)`, read by that path;
  - `tests/test_triad_lane_tooling_handoff.py`: the tree id with no path, and the unreadable record.
- **Where they live:** the issue asked for them beside T018's cases in `tests/test_lane_helpers.sh`. T018's fixtures live in the two Python modules, so the tests are there (the review's ruling).

**The order with #252 (the review's NIT 5).** `<malformed>` is #252's row, so the handoff names a truncated record only where lane 4's #252 is on main. The review asked for #252 first, and that is what happened: #252 merged as `00d52ae` at 2026-10-19T18:14Z, and q6k is built on it. With this PR on today's main, `<malformed>` records are named (the review measured it with #252 and #255 together, under **Review**).

**Follow-ups, not in this PR:**
- a declared leg under an unsearchable parent (the review's ruling on lane 2's doubt: a follow-up);
- a leg at mode 0100, searchable and not listable, which reads `clean` (finding 4, estate-wide; lane 3 proposes it to Brett Heap for v1.2.0, #255 6272494888);
- the top block's contradiction for a paired tree that only a broken record names (finding 3; proposed for v1.2.0 in the same comment);
- the handoff's wording on #252's refusal code (NIT 8; lane 3 proposes it as a v1.2.0 item, #255 6272494888).

**Runs**
- **The record, q6k** (branch `lane3-queue/q6k-255-on-q5k`: q5k + this head `a16e534`, on main `677acc0`, tree `1149aeb`, with its conflict with #283 resolved as in `fcd3cce`): `tests.yml` run 39373762988, green. This is the run of record for the landing.
- **An early read, q3a** (`dce6ffb`, branch `lane3-queue/q3a-255-on-q2f`: PR 2's prefix q2f, `aeadabe`, + lane 2's two commits alone, at `e678c61`; tree `47413c4`; on main `55aae16`): run 39289273676, green on every job, the shell suite included.
- **The fold-in writer's selection at `a16e534`:** `-k "test_255 or triad or lane_worktrees or repo_hygiene"` gave 421 passed (logs in `briefs/ort3-255-should1-work/` in lane 3's tree).
- **The fold-in's mutants:** the writer ran seven and each fails a `test_255_` test.
  - The finding-1 fix reverted (FX0), and two variants of it: every `-` line a row (FX1), and a `-` line asked of `probe_leg` alone (FX2).
  - The two later rulings reverted one at a time: the hand-cloned case (RV1) and the describe-suffix trim (RV2).
  - The reviewer's MS5 and MS6.

**Review.** Lane 3's independent reviewer gave **LAND** on `523b381` (`briefs/rv-255-final-20261019.md` in lane 3's tree, posted on #255 as 6272494888). Both fixes do what sections 1 and 2 ask, in every reader named.
- **What it ran:**
  - the four new tests fail on `5dd0df0` for the right reason and pass on `523b381`;
  - 17 of 19 mutants are killed, and the two survivors, MS5 and MS6, are low-impact (the fold-in's tests now kill both);
  - with #252 (`3ffefce`) the fix merges cleanly, and against the real `lanes-edit.sh` the handoff names the `<malformed>` record, in 5 of 5 cases. On that tree, main + #252 + #255, the selection `-k 'test_255 or triad or lifecycle_fail_open or repo_hygiene or lane_worktrees'` gave 457 passed;
  - the shell suite on that tree gave 4889 passed and 2 failed, `test_lane_helpers.sh:1269` and `:8548`, both in `lane-start`'s row stamp. The reviewer takes them as its sandbox's 106-character path reaching the 240-character `ROW_STATE_CAP`, and neither #255 nor #252 touches `lane-start` or the cap. q3a is green on every job, the shell suite included.
- **What lane 3 did with the findings:**
  - finding 1, a SHOULD, and NITs 6 and 7: folded in, above;
  - finding 2, a SHOULD: the cherry-pick, above;
  - findings 3 and 4, and NIT 8: the follow-ups, above;
  - NIT 5: the order with #252, above.
- **The reviewer's re-check of `e678c61..a16e534` is LAND (#271 6277159862).**

**Landing:** Brett Heap's decision 4 on #228 ("Yes, merge when green (Recommended)"), and his word of 2026-10-18T20:3xZ, "use this as approval to merge when green for all merges thru v1.1.0".
- Lane openRepoTools-3 lands the queue in one order: #262, PR 2, PR 3, PR 4, T025's docs PR, then #255.
- This PR squash-merges after an independent review, once Linux CI is green on the exact tree main will have after its squash. The record is a queue run of `tests.yml` on lane 3's prefix branch, under **Runs**.
- #255 is last in the queue. q6k is q5k plus this head, so PRs 2 to 4 and T025 are beneath it.
- When its turn comes, lane 3 pushes a head that merges main into `a16e534` with q6k's tree, as a fast-forward, so the tree under review is the tree that was tested.

Lane: openRepoTools-3

Landed by lane openRepoTools-3 as queue prefix q6k (tree 1149aeb, tests.yml run 39373762988 green), on Brett Heap's word of 2026-10-18T20:37:07Z: "use this as approval to merge when green for all merges thru v1.1.0". Its conflict with #283 in tests/test_triad_lane_tooling_handoff.py is resolved as in the tested tree fcd3cce, which keeps both blocks. Independent re-check of the fold-in round e678c61..a16e534: LAND (#271 6277159862).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 20, 2026
…271) into act 2's slice 1a (#198)

One conflict, in lanes-edit.sh's lane_state_follow, at the check of a
snapshot this helper cannot read: this branch releases the workstation's
sidecar lock (sidecar_release); main released W's mutex where the follow
took it (lsf_own) and gave a new note naming $LSS_WHY. Resolved as the PR
says: keep sidecar_release, take main's note. No line of main's that merged
on its own uses lsf_own or release_lock inside lane_state_follow, and main's
lanes-edit.sh changes since 55aae16 add no lock call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 21, 2026
…rea, and add the lines for #263, #289, #294 and #296, the changes after a42946e (#228)

`docs/release-notes/v1.1.0.md` takes the fixes of lane 3's review of d8a349d (#293 comments 6314185715 and 6314529807, the second of which replaces three of the corrected texts of the first) and the lines for the changes after a42946e.

The review's fixes:
- Line 45 (#255, #271) no longer says that `lane-reconcile` and `lane-handoff` read a dependency nested in a leg: only the sweep and `lane-end` do. It says that a leg directory this account cannot search is unreadable and never `absent`, that a nested dependency is unreadable to the sweep and `lane-end` too, and that the sweep still reads a declared leg beneath such a directory as `absent` (#272, open). It also says NOT POLLED where the tool does.
- Line 46 (#280, #283): an `awk` exits, it does not return, and the sentence on a status of 0 or 8 is gone.
- Line 61 (#214): the fence never checks whose a local commit is, only that it adds or changes a register or handoff file.
- The `<other-changes>` item counts 44 of the 53, because the openRepoShape pin's line (#233) is in the list, and the other 9, not 43 and 10. The milestone item names #247 and lists the six entries of 2026-10-20.
- #209 is not tests-only: its `-e` fixes `lanes`' stderr filter under macOS's BSD grep, so it moves from the test suite to the lane area, and its one line there carries its test changes too. It is cited once.

The lines for the changes after a42946e, each in its area and worded from its squash commit and description: #263 (d9065df) and #289 (fb2cf36) under the register and `lanes-edit.sh`, #294 (acc89ea, which closes #292) under `lane`, `lane-start`, `lane-end` and the name guard, and #296, which pins every Ubuntu runner to ubuntu-24.04, under the test suite and its checks. #296 has not landed yet: lane 3 lands it right before #293. The `<other-changes>` item says the lines were added on 2026-10-21 and are not in its counts, and the placeholder stays for #293 itself.

Not touched: lane 3's optional sentence on #271 and its NIT on the tag.

Lane: openRepoTools-2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 21, 2026
… 1.1.0 and list, by area and from the squash titles of aaa7bc8..a42946e, what else changed on main since v1.0.0 (#228) (#293)

Part of #228. It brings `docs/release-notes/v1.1.0.md` up to date on main: first on `a42946e`, as lane 3 asked on Brett Heap's word (#228 comment 6303608345, the section for lane openRepoTools-2, task 1), then with lane 3's review of `d8a349d` (comments 6314185715 and 6314529807 on this pull request), with the lines for the three changes that landed after `a42946e`, and with the line for #296, which landed right before this pull request. It is one file, changed in two commits (`d8a349d` and `bebb77d`), 59 insertions and 4 deletions against main.

Lands last before rehearsal D's freeze; lane 3 reviews and merges it (#228 6303608345)

**What changed**
- **Lane 3's review is applied,** in the second commit:
  - **The line on #255 and #271** no longer says that `lane-reconcile` and `lane-handoff` read a dependency nested in a leg, since only the sweep and `lane-end` do. It says that a leg directory this account cannot search is unreadable and never `absent`; that a dependency nested in a leg whose directory, or one above it, it cannot search is unreadable to the sweep and `lane-end` too; and that one known gap remains, #272, where the sweep reads a declared leg beneath such a directory as `absent`, so `lane-end` does not hold on it. It also says NOT POLLED where the tool prints it, and it says that the item closes two gaps in #246, where it said "both gaps" with nothing before it to refer to.
  - **The line on #280 and #283:** an `awk` exits and does not "return", and the sentence on a status of 0 or 8 is gone, because a reader of the notes cannot act on it.
  - **The line on #214:** the fence passes a local commit only when it adds or changes a register or handoff file, and never checks whose it is, so the line no longer says "the lane tooling's own".
  - **The counts in the `<other-changes>` item:** the pin's line (#233) is in the list, so the list carries 44 of the 53, and the other 9 have their lines under "What 1.1.0 adds", not 43 and 10. The item says "the openRepoShape pin" where the runner pin of #296 could be taken for it.
  - **The milestone item** names #247, so it lists the six entries the milestone held on 2026-10-20.
  - **#209 is not tests-only.** Its `-e` fixes `lanes`' stderr filter under macOS's BSD `grep`, so it moves from the test suite to the lane area, after #208's, and its one line there also carries its test changes: the `repoG#8` assertion and the count of bash files in prose. It is cited once.
- **Lines for the changes after `a42946e`:** #263 (`d9065df`), #289 (`fb2cf36`) and #294 (`acc89ea`, which closes #292) landed on 2026-10-21, from 11:28Z to 11:30Z. Each has a line in its area, worded from its squash commit and its description: #263 and #289 under "The register and `lanes-edit.sh`", and #294 under "`lane`, `lane-start`, `lane-end`, the name guard and their helpers".
  - **#296 has its line too,** last under "The test suite and its checks": CI and the release workflow run on `ubuntu-24.04`, because `ubuntu-latest` is now Ubuntu 26.04, and `macos-latest` is unchanged. It landed right before this pull request, as `5afba4a`.
- **"What 1.1.0 adds" gains two lines,** after the annotated-tags item. They are for the three entries that closed in the v1.1.0 milestone after the draft was written:
  - **#255,** fixed by #271: the lane tools name what they could not read in a triad's legs. `lane-handoff` names every tree-inventory record that is not a whole schema-1 one, and a leg directory this account cannot search is unreadable and never `absent`. Only the sweep and `lane-end` also see a dependency nested in such a leg, since `lane-reconcile` and `lane-handoff` read declared legs only, and #272 stays open as the known gap.
  - **#280,** fixed by #283: `lane-handoff` reads the lane's lifecycle whole and parses it afterwards, so an `awk` that exits early can no longer turn `lane-state`'s SIGPIPE into a lifecycle that could not be read.
  - #283 is #280's pull request, so the three numbers make two lines. Each line cites the issue and its pull request, as the hook-pair line does for #237 and #247.
- **"What else changed since v1.0.0" is drafted** from `git log aaa7bc8..a42946e`, which is 53 squash commits, one pull request each and no merge commit. It is grouped by area, one line each, and each line ends with its pull request. It lists 44 of the 53: the pin's line (#233) at its head and 43 by area, as 4 on the estate commands, 10 on the register and `lanes-edit.sh`, 11 on `lane`, `lane-start`, `lane-end` and the name guard, 6 on the handoff and its skill, 1 on the lane tools in a triad's legs (#246), 9 on the test suite and its checks, and 2 on the triad migration's records.
  - **The other 9 are accounted for elsewhere:** the package series of #228 (#262, #267, #268, #269, #270 and #284) is covered by #228's lines, and #247, #271 and #283 have the lines above.
  - **The lines added on 2026-10-21** are for the four changes after `a42946e`: #263 and #289 on the register and `lanes-edit.sh`, #294 on `lane`, `lane-start`, `lane-end` and the name guard, and #296 on the test suite and its checks. The areas now hold 47 lines, one for each of 47 pull requests, and #233's line at the head of the list makes 48.
  - **The list is redone at the tag.** It is drafted from the squash titles only.
- **"Before the tag" is updated in three items:**
  - `<pr-count>` gives the count at `a42946e`, which is 53;
  - `<other-changes>` says what was drafted, which 9 pull requests have their lines under "What 1.1.0 adds", that the lines for #263, #289, #294 and #296 were added on 2026-10-21, and what is left to do at the tag; the placeholder's own line now stands for the changes after `a42946e` that have no line above;
  - the milestone item names the six entries the milestone held on 2026-10-20: #228 still open, #237 closed by its pull request #247, #255 closed, and #280 closed by its pull request #283.

**Not added:** nothing that has landed. #296 landed right before this pull request and has its line. This pull request itself, and whatever lands after it, is what the `<other-changes>` placeholder stands for. None of #263, #289, #294, #296 and #292 is in the v1.1.0 milestone, which still holds the six entries of 2026-10-20.

**How this keeps the draft's rules**
- **One-line paragraphs:** every added line is its own list item, and none is wrapped.
- **References:** every reference above "Before the tag" is spelled `opensoft/openRepoTools#N`, with no bare `#N`, since the notes are published from another repository after the migration.
- **Placeholders:** `<other-changes>` stays as a named placeholder in code font, as the closing line of the list and as its checklist item. `<pr-count>` is unchanged apart from the drafting-time count.
- **The file's closing lines are untouched:** the self-naming last line stays last.

**Tests**
- **T023's verify command** passes: the last line names `openRepoTools-spec` and `docs/release-notes/v1.1.0.md`, `## Before the tag` is there, and so is `pipx install --force openrepotools`.
- **Coverage:** a script over `git log aaa7bc8..acc89ea`, 56 commits and no merge, finds each pull request cited above "Before the tag" except the six of #228's package series, which #228's lines cover. That is 46 by area, #233 at the head of the list, and #247, #271 and #283 under "What 1.1.0 adds". #296 is not in that range, and its line cites it.
- **Hygiene:** by grep over `tests/`, no test names this file. The two that read its content scan every tracked file, and both are in `tests/test_repo_hygiene.py`. The only other test that names `release-notes` is in `tests/test_release_workflow.py`, and it reads `release.yml`, not the notes. All of `tests/test_repo_hygiene.py` was run on the file, through `tests/run.sh` with every `LANES_*` and `TMUX*` variable unset:

  ```text
  -k 'repo_hygiene' -v
  collected 2933 items / 2696 deselected / 237 selected
  237 passed, 2696 deselected in 2.59s
  ```
- **Not run:** the whole suite. CI's `tests` job is the suite of record. This pull request's own runs on `bebb77d` went red on `ubuntu-latest`, which is Ubuntu 26.04, for the reasons #296 gives, because #296 was not yet on main. The record is lane 3's queue run over main, #296 and `bebb77d`, on `ubuntu-24.04` (below).

**Where the draft's rules left room, and lane 3's answers (comment 6314185715)**
- **Which section holds #255 and #280:** "What 1.1.0 adds", because the milestone is Brett Heap's scope for the release and #237 with #247 already sit there.
- **`<other-changes>` stays** as a closing placeholder after the drafted list, since this pull request itself lands after `a42946e`.
- **How much to list:** the tests-only and migration-record lines stay through the tag, because the check that every pull request has a line depends on them. If Brett Heap wants a shorter body, they fold into one line that still lists every pull request number.
- **Issues and pull requests:** the milestone lines cite both, and the area lines cite the pull request only.

**Lane 3's re-check of `bebb77d`** (#293 6315668503) is MERGE AFTER FIXES.
- **What holds:** every line lane 2 listed, the recount behind line 140 (44 of the 53 pull requests in `aaa7bc8..a42946e`, and 56 in `aaa7bc8..acc89ea`), the file's rules, and T023's verify commands.
- **One SHOULD and two NITs, all wording:**
  - line 45's sentence on a dependency nested in a leg, where "one above it" reads as a directory above the leg, which is #272's case. The text was lane 3's own;
  - "One known gap" in the same line;
  - line 74's last clause.
- **Why they wait:** lane 3 merges the notes as tested and carries the three fixes into the notes' final pass at the tag. A push here would cost another CI round while lane 2 is out of usage until 15:30Z, and the notes are edited again at the tag in any case.

Lane: openRepoTools-2

Landed by lane openRepoTools-3, as queue prefix `lane3-queue-u/p2-293` (tree `9b879ca`; tests.yml run 39595166877, green on `ubuntu-24.04`), right after #296. Before the landing, lane 3 merged main into the branch, so that its own checks ran over main with #296; the content is unchanged. Brett Heap's word, of 2026-10-20T22:5xZ: "merge all when green for all merges thru v1.1.0. use this for all lanes" (#228 6303614082).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 22, 2026
…4.04 with an awk that acts at the first record, and no longer says that awk is gawk, which is not confirmed (#253)

The completion review of 2be60ea (its NIT C) found that the comment at
tests/test_lane_helpers.sh:14985 said ubuntu-24.04's awk is gawk, and that
nothing on record says so:
- the runner-images manifest at tag ubuntu24/20261012.334, the image of the
  two red runs, has no gawk, mawk or awk entry, and its apt table has no
  gawk; a grep of all 123 files under images/ubuntu/{scripts,toolsets} at
  that tag gives no hit;
- no CI log prints awk's flavor;
- lane 3's "manifest lists gawk" (#280 comment 6280603973) is not borne out
  at that tag;
- the image of the lane5-ci/253 run, 20261018.339.1, has no published tag.
What the two red runs do show is the 141 signature. Plain mawk reads an
11-line snapshot to its end, so it cannot give that signature, and an awk
that acts at the first record does, as real gawk 5.3.2 and
`mawk -W interactive` did in the first review's runs. The comment now says
that, and nothing more about which awk the image carries.

WHAT CHANGES is the comment at tests/test_lane_helpers.sh:14985-14989 and
nothing else: "whose awk is gawk" reads "with an awk that acts at the first
record (which awk is not confirmed)", and "gawk hands the `state` line"
reads "Such an awk hands the `state` line". The words need one more comment
line, so every line below moves down by one: the padding line is now :15017,
the unasserted set-lane-state :15035 and the premise :15038.

THE PROOF is of a comment-only change, so no test was run for this commit;
CI runs the whole suite after the push. Against 2be60ea:
- the diff is 5 lines added and 4 removed, and every one is a full-line
  comment (hunk -14985,4 +14985,5);
- with the comments stripped the file is byte-identical, by two methods.
  Removing every full-line comment leaves 790849 bytes, sha256 9331f2fd…,
  in both. The whole file as a function body, read by bash's own parser and
  printed by `declare -f`, gives 837924 bytes, sha256 8a1115d0…, in both,
  and the same method differs when one code byte is changed (32768 to
  32769) and when one byte is changed inside a heredoc's comment line;
- `bash -n tests/test_lane_helpers.sh` exits 0 and `git diff --check` is
  clean.

2be60ea's message carries the same wording. The squash body replaces it
with the corrected one, and corrects the CI counts (NIT A), adds what the 13
red runs share (NIT B) and stops calling the padding #283's technique (the
first review's NIT 1).

Lane: openRepoTools-5
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant