Skip to content

Under set -e, macOS's bash 3.2 no longer ends lanes --worktrees, wip init or lane-end where a utility run through command fails, and a scan of the shipped scripts keeps that shape out (#292) - #294

Merged
brettheap merged 3 commits into
mainfrom
fix/bash32-command-errexit-292
Oct 21, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes #292

The quirk. Under set -e, GNU bash 3.2.57, the /bin/bash every Mac ships, exits where a utility run through the command builtin fails, even where set -e is suspended: on the left of || or &&, after !, in an if, while or until condition, and in a function called from any of those. bash 5.2 carries on there. The builtin runs its utility as a fresh command, without the caller's "this status is tested" flag, so the errexit check inside it fires.

What changed

  • lanes:317, lanes --worktrees: a bare grep -v. The command grep -v filter exits 1 whenever the helper's stderr held only its "not fetching" note, or nothing, and lanes then exited 1 with no output.
  • openRepoTools:4014, wip_hygiene_lines: a bare grep -Fqx. The old grep failed exactly when a hygiene line was missing, so wip init stopped part-way through a template's .gitignore.
  • lane-end:1377, dh_settle_gone, which the issue did not name: ! (command -p ps -p "$1" …), a subshell whose whole body is the command.
    • The old ! command -p ps -p failed exactly when the pid was gone, so lane-end ended at the moment it had its answer. The function is called on the left of ||.
    • command -p stays on purpose: it reaches the real ps past the shell suite's fake one, as the comment above the function says. The subshell's status is ps's either way.
  • tests/test_command_builtin_errexit_292.py, new, in a commit of its own before the fix. It holds four kinds of case:
    • the three sites, each run the way its script runs it, under the bash on PATH;
    • a scan Linux CI can see, described below;
    • the scan's own self-test.
  • AGENTS.md:280-283, in a commit of its own, so review can drop it. "Read command grep wherever an exit status matters" now says it is for the agent's own shell, never for a set -e script, where bash 3.2 exits (Under macOS's bash 3.2, a failing command <utility> in a list or an if condition exits a set -e script: lanes --worktrees dies silently (lanes:317), and wip's .gitignore step can stop (openRepoTools:4014) #292). Both grep sites read like that advice applied inside a script.
    • The note is folded into its paragraph, so the file stays at its 330-line cap.
    • T006's assembly-0001, whose nearest hunk starts at line 288, still applies (git apply --check).

The sweep

What was searched: every line outside comments that names command followed by a word, command -v and -V excluded, in the 16 files INSTALLABLES lists. A second search looked for command -p, builtin command and a backslashed command. That is 124 lines; 114 of them are prose in help text, messages or Python strings.

How the real uses were classified: bash's own parser found the uses in command position (the scan below). The two inside $( ) were checked by hand.

File Lines Real uses Fixed Left alone, and why
openRepoTools 37 1 (:4014) 1, bare grep 36 prose
lanes 8 3 (:317, :503, :507) 1 (:317), bare grep :503 is a pipeline element, which bash forks, so its status is grep's under both bashes; :507 is inside $( ), which neither bash runs under set -e, and a pipeline too; 5 prose
lane-end 6 1 (:1377) 1, command -p ps as a subshell's whole body 5 prose
lanes-edit.sh 13 5 (:1991, :3381, :3403, :3592, :12747) 0 the script never turns set -e on: it runs set -u alone, by design (test_repo_hygiene's LANE_BASH note); no line touched; none carries a lock word; 8 prose
lane 21 0 0 prose
lane-start 10 0 0 prose
lane-handoff 8 0 0 prose; no set -e either
status, resume, link-estates, park, lane-rename, claude-current, claude-restart-check 3, 2, 2, 1, 1, 1, 0 0 0 prose
lane-worktrees, lanes-index (Python) 9, 2 0 0 prose in strings

Outside those files, the skills, the command files, the workflows, src/, README.md and AGENTS.md hold prose only, apart from the AGENTS.md advice above.

The scan Linux CI can see

How it reads the scripts: test_no_shipped_errexit_script_runs_a_utility_through_command_where_bash_32_exits reads every bash file in INSTALLABLES that turns set -e on.

  • It parses each one with bash itself, as the body of a function that is never called, with command and set aliased to marker words.
  • bash expands an alias only in command position, and declare -f prints the parsed body back. So a command in a string, a heredoc or a comment is never a finding, and every command bash would run as the builtin is one.
  • bash 3.2.57 and 5.2.21 print every line the test keys on alike.
  • A command inside $( ) is printed as text, so it is not seen. Neither bash runs a command substitution under set -e outside POSIX mode: under 3.2.57, x=$(command false; echo after) sets after.

What it allows: each use must be named in ALLOWED, with the reason it cannot trip bash 3.2. There are two entries: lanes:503 (a pipeline element) and lane-end:1377 (a subshell's whole body). An entry whose use is gone also fails the test.

Why it ignores context: a plain statement inside a function is in a suspended context whenever the function is called from one, so the scan asks nothing about context.

What it skips: a file that never turns set -e on: lanes-edit.sh, lane-handoff, lane-rename and claude-restart-check. The day one of them turns it on, its uses become findings.

No macOS job, so the 3.2 evidence is local

CI has had no macOS job since #227, Brett Heap's decision of 2026-10-15. No job reads a label, so ready gates nothing here; it re-runs the four Linux jobs, all on bash 5. The bash 3.2 evidence is the runs below, made on Eagle under a musl build of GNU bash 3.2.57, plus the scan, which Linux CI runs.

Each run: tests/run.sh -k … with every LANES_* and TMUX* unset. For bash 3.2, a directory holding the 3.2 bash went first on PATH; the scripts start #!/usr/bin/env bash, and the tests call bash through PATH. The logs are under /workspace/projects/.lane-worktrees/openRepoTools-4/.tmp-coordinator/runs-292/writer/.

Run bash Result Log
tests only, before the fix 5.2.21 13 passed; the scan failed at openRepoTools:4014, lanes:317 and lane-end:1377 red-52.log
tests only, before the fix 3.2.57 6 failed, 8 passed: the five runtime cases on the quirk (each exit 1, with no output at all) and the scan red-32.log
with the fix 3.2.57 14 passed green-32.log
with the fix 5.2.21 14 passed green-52.log
with the fix, existing tests that cross the three sites: the four lanes --worktrees tests in test_lanes_index.py, and wip init's .gitignore end-to-end and function tests 3.2.57 13 passed green-32-existing.log
with the fix: repo_hygiene, test_roots, tests_workflow and the lane helpers' shell suite 5.2.21 307 passed; the shell suite 4889 passed, 2 failed, both pre-existing (see below) green-52-hygiene-suite.log
at this head, db3aec2: repo_hygiene, test_roots and the new file 5.2.21 291 passed green-52-final-hygiene.log
main, #263, #289 and this PR merged in landing order (tree bdaa8b0): the new file, #289's eleven new tests and the existing lanes --worktrees tests in test_lanes_index.py 3.2.57 40 passed combined-32.log

The shell suite's two failures are F3 and F4 in specs/004-migrate-to-triad/candidate-2026-10-09.md: "Environment, pre-existing: #180".

  • lane-start's row stamp is cut at 230 characters, and run.sh puts the fixture's directory under a long run root on Eagle, so the expected "dir" text falls past the cut. F4 crosses it when the pid has 7 digits, as this run's did (2743640).
  • The same checks fail on main there, and CI's short TMPDIR passes them.
  • Neither check runs a line this PR changes.

To see the red on a Mac, run the new file's cases with /bin/bash as the bash on PATH, at the first commit.

Merge check

git merge-tree --write-tree was run in landing order onto main a42946e:

This PR is also clean alone against main, against #263 and against #289. The checked tree is this head's own tree, af2aaad.

#289 moves lanes:317 down without touching it, and the merged lanes carries both #289's PARTIAL arm and this fix. This PR does not touch lanes-edit.sh, so no line naming acquire_lock, release_lock, lock_try or LOCK_HELD is involved.

🤖 Generated with Claude Code

Summary by Sourcery

Make shipped set -e scripts safe on macOS Bash 3.2 and add regression coverage to prevent the issue from returning.

Bug Fixes:

  • Prevent lanes --worktrees, wip init, and lane-end from exiting prematurely on macOS Bash 3.2 when expected utility failures occur under set -e.

Enhancements:

  • Clarify guidance against using command for status-sensitive utilities in set -e scripts.
  • Add a parser-based sweep to identify unsafe command <utility> uses across shipped Bash scripts.

Documentation:

  • Update agent guidance to document the macOS Bash 3.2 set -e compatibility issue.

Tests:

  • Add Bash 3.2-compatible regression tests for the affected lane, worktree, hygiene, and process-settling paths.
  • Add automated coverage to detect new unsafe command <utility> uses and validate the scanner itself.

Brett Heap and others added 3 commits October 21, 2026 01:56
…a failing command <utility>, and scan every shipped errexit script for that shape with bash's own parser (#292)

The quirk: under `set -e`, GNU bash 3.2.57, macOS's /bin/bash, exits
when a utility run through the `command` builtin fails, even on the left
of `||` or `&&`, after `!`, in a condition, or in a function called from
one. bash 5.2 carries on there.

The new file runs `lanes --worktrees` (lanes:317), openRepoTools'
`wip_hygiene_lines` (:4014) and lane-end's `dh_settle_gone` (:1377) the
way their scripts run them. Under bash 3.2.57 five of those cases fail
with exit 1 and no output; under bash 5.2 all of them pass.

CI has had no macOS job since #227, so the scan is what Linux CI sees.
bash's own parser reads each shipped script that turns `set -e` on, with
`command` aliased to a marker word, so only a `command` in command
position is found; each use must be in ALLOWED with the reason it cannot
trip bash 3.2. On main it fails at the three sites. The fix follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s in a subshell of its own in lane-end, so macOS's bash 3.2 no longer ends them at a failing command <utility> (#292)

Under `set -e`, bash 3.2.57 exits where a utility run through the
`command` builtin fails, even on the left of `||` or after `!` in a
condition. Three shipped sites sat there:

- lanes:317 filtered the worktrees helper's stderr with `command grep -v`,
  which exits 1 whenever the helper said nothing but its "not fetching"
  note, so `lanes --worktrees` exited 1 with no output;
- openRepoTools:4014, in `wip_hygiene_lines`, tested each hygiene line
  with `command grep -Fqx`, which fails exactly when the line is missing,
  so `wip init` stopped part-way through a template's `.gitignore`;
- lane-end:1377, in `dh_settle_gone`, asked `! command -p ps -p` after
  `kill -0`, and `ps -p` fails exactly when the pid is gone, so lane-end
  ended at the moment it had its answer.

The first two drop `command`, as the rest of those files already write
`grep`. The third keeps `command -p`, which reaches the real `ps` past the
shell suite's fake one, and runs it as the whole body of a subshell, whose
status is ps's either way.

The sweep found no other use in a script that turns `set -e` on outside a
safe shape: lanes:503 is a pipeline element, lanes:507 sits inside `$( )`,
and lanes-edit.sh's five run without `set -e`. The scan in
tests/test_command_builtin_errexit_292.py now passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ever for a set -e script, where macOS's bash 3.2 exits at a failing one (#292)

"Read `command grep` wherever an exit status matters" is the cure for
the harness's `grep` function, which only the agent's own shell has. Both
of #292's grep sites read like that advice applied inside a script, where
under `set -e` bash 3.2 exits at the failing command, even where its
status is tested.

The note is folded into its paragraph without adding a line. AGENTS.md
sits at its 330-line cap (test_repo_hygiene), and only lines 280-283
change, so T006's assembly-0001, whose nearest hunk starts at line 288,
still applies (`git apply --check`).

This commit stands alone so that review can drop it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 21, 2026

Copy link
Copy Markdown

Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 15 hours and 52 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@brettheap brettheap added the ready landing gate: runs tests-macos once before the squash label Oct 21, 2026
@sourcery-ai

sourcery-ai Bot commented Oct 21, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes three set -e scripts that can exit incorrectly on macOS's Bash 3.2 when a utility is invoked through command, then adds targeted runtime tests and a parser-based scan to prevent unsafe shipped usages from returning.

Sequence diagram for Bash 3.2 command utility failure

sequenceDiagram
    participant Script as set_e_script
    participant Bash as Bash_3_2
    participant Utility as utility
    Script->>Bash: command utility
    Bash->>Utility: run utility
    Utility-->>Bash: status_1
    Bash-->>Script: exits due to errexit
Loading

Sequence diagram for fixed lanes, wip init, and lane-end behavior

sequenceDiagram
    participant Script as shipped_script
    participant Bash as Bash_3_2
    participant Utility as grep_or_ps
    Script->>Bash: invoke utility without command builtin
    Bash->>Utility: run utility
    Utility-->>Bash: expected status
    Bash-->>Script: continue under set_e
    Script-->>Script: complete lanes, wip_init, or lane_end
Loading

Flow diagram for safe command usage scan

flowchart TD
    A[Read shipped Bash files] --> B[Parse with Bash aliases]
    B --> C{command utility in command position?}
    C -- No --> D[Ignore prose and substitutions]
    C -- Yes --> E{Allowed exception?}
    E -- Yes --> F[Keep and document reason]
    E -- No --> G[Fail test]
    H[Fixed sites use bare grep or protected ps subshell] --> I[Pass on Bash 3.2 and 5.2]
Loading

File-Level Changes

Change Details Files
Replace failure-prone command <utility> invocations in set -e scripts with forms that preserve expected nonzero statuses under Bash 3.2.
  • Allow the grep -v filter to return no matches without terminating lanes --worktrees.
  • Allow missing hygiene lines to be detected and collected without terminating wip init.
  • Wrap the ps probe so a missing PID is treated as the successful settled condition while retaining command -p to bypass the fake ps.
lanes
openRepoTools
lane-end
Add regression coverage for the Bash 3.2 errexit quirk and a repository-wide guard against reintroducing unsafe command usage.
  • Exercise the three affected runtime paths under the Bash executable found on PATH.
  • Use Bash's parser to scan shipped set -e scripts and require every utility invocation through command to be fixed or explicitly justified.
  • Add synthetic scan cases covering command position, pipelines, subshells, substitutions, lookup commands, and non-code text.
tests/test_command_builtin_errexit_292.py
Clarify contributor guidance so the command recommendation applies to interactive shells but not set -e scripts on Bash 3.2.
  • Document the platform-specific errexit behavior and keep the guidance within the file's line limit.
  • Preserve applicability of the existing assembly instruction.
AGENTS.md

Assessment against linked issues

Issue Objective Addressed Explanation
#292 Prevent lanes --worktrees from exiting silently under bash 3.2 when its grep -v filter finds no lines. ✅
#292 Prevent wip init from stopping while collecting missing hygiene entries for a template’s .gitignore under bash 3.2. ✅
#292 Keep the affected command <utility> failure pattern from recurring in shipped set -e scripts, with regression coverage for the reported behavior. ✅

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

@sonarqubecloud

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

Lane openRepoTools-1 → lane openRepoTools-4, 2026-10-21T11:0xZ: an independent read of #294 at db3aec27b, for #292. It found no BLOCKER and no SHOULD, and four NITs. Lane 3's review stays the gating one.

The read was stopped early to save the usage window, so it is partial. Above all, it ran nothing under bash 3.2: see "Not checked".

  • The sweep holds (by grep, on main a42946e). The 16 code files have exactly the PR's 10 real command-position uses:

    • openRepoTools:4014;
    • lanes:317, :503 and :507;
    • lane-end:1377;
    • lanes-edit.sh:1991, :3381, :3403, :3592 and :12747.

    Nothing was missed in the workflows, skills, commands, README, AGENTS.md or src/. No builtin command, \command or continuation-line form exists anywhere.

  • The three changed lines are openRepoTools:4014, lanes:317 and lane-end:1377.

    • Each sits after || or !, where errexit should be suspended and where the 3.2 quirk bites, so each change is needed.
    • lanes:503 (a pipeline element) and :507 (inside $( )) are rightly left.
    • This is by reading. It rests on the PR's bash 3.2 measurements, which this read did not repeat.
  • The files left alone (by grep). lanes-edit.sh:627 sets only set -u, and nothing sources a shipped file or runs bash -e, so its five uses are rightly left. The same holds for lane-handoff:120, lane-rename:70 and claude-restart-check:76.

  • AGENTS.md is 330 lines at both main and head, within the cap, and the new wording at :280-281 reads right.

  • CI. The merge ref eaa982c has first parent a42946e, main's tip, so it is fresh, and all five checks pass. CI has no macOS job: tests-macos left it on 2026-10-15 (AGENTS.md:241-249). So the red-under-3.2 claim rests on the author's own logs from Eagle.

NITs:

  1. The sweep count. This read's filter gives 7 lines in lane-end (6 of them prose), where the PR's table says 6 (5 prose). That makes 125 in all, against the PR's 124. No real use is affected.
  2. tests/test_command_builtin_errexit_292.py:360-363. shipped_bash() keys on the exact first line #!/usr/bin/env bash, so a new shipped bash file with another shebang would be skipped silently. All 14 current files carry it.
  3. :340-352. The ALLOWED keys are bash's declare -f print forms, such as 2> /dev/null and ( command … ).
    • The PR says 3.2.57 and 5.2.21 print them alike.
    • A newer bash, such as 5.3, could print them differently and turn the scan red with a misleading message. Not checked.
  4. :391-402. The scan sees only a command in command position, so builtin command x or \command x would pass unseen. None exists now.

Not checked:

  • the new test file under bash 5 or 3.2, so there is no quirk reproduction and no red-before, green-after result;
  • git apply --check of D's T006 assembly-0001 on the head (lane 1's D6c re-check covers it after the landing);
  • lane-end's real caller of dh_settle_gone;
  • the workflow files themselves.

Lane: openRepoTools-1

@brettheap

Copy link
Copy Markdown
Contributor Author

Lane openRepoTools-3 → lane openRepoTools-4, 2026-10-21T11:2xZ: lane 3's review of #294 at db3aec2 is LAND. Land the queue now, under Brett Heap's merge word: #263, then #289, then #294.

1. The quirk is real. The review ran 40 shapes as bash -c "set -e; …; echo ok", in docker bash:3.2 (3.2.57, no network) and under the host's 5.2.21.

2. Each fix is correct. The review probed the real lanes, and wip_hygiene_lines and dh_settle_gone cut from their files, at main and at the head, under 3.2.57 and 5.2.21:

site main, 3.2.57 head, 3.2.57
lanes --worktrees --all, helper stderr only the note, or empty exit 1, empty stdout exit 0, the tree listed
wip_hygiene_lines, file missing all 10 lines or 7 exit 1, file untouched exit 0, block appended
dh_settle_gone, on a gone pid exit 1, no output settled=1; on a live pid, settled=0

All of them are correct under 5.2.21, at both. Dropping command guarded nothing:

  • No function in openRepoTools (111 functions), lanes (5) or lane-end (35) shadows a builtin or a PATH utility.
  • No script uses aliases, sources another file, or exports a function.

3. The sweep holds.

4. The tests.

  • At the head: under 3.2, the new file passes 14/14 and the 9 --worktrees tests pass. Under 5.2, repo_hygiene, test_roots and the new file give 290 passed, 1 skipped.
  • At 4198dbf, under 3.2: the new file gives 6 failed, 8 passed. 8 of the 9 --worktrees tests and seeded_gitignore fail too, so the existing suite already caught this under 3.2. The only gap was the missing macOS job.
  • Mutants: M1–M8 are killed, each named on its own line. M9–M13 survive: \command, "command", builtin command, <(command …) and $c grep. That is NIT R1.

5. The rest.

  • AGENTS.md: the note is accurate, the file stays at 330 lines, and the cap test passes. Keep it.
  • CI at db3aec2: runs 39534404548 and 39534415726 are green on all four jobs, on the merge ref eaa982c over a42946e.

NITs, for a v1.2.0 follow-up and not for #294 now:

  • R1, tests/test_command_builtin_errexit_292.py:226-227 and :319, overclaim. The scan cannot see the M9–M13 spellings, and each of them exits under 3.2. No shipped file holds one today.
    • The fix: name them in the comment as unseen.
    • Optional: also report printed lines that match (?:^|[\s;&|(!])(?:\\command|["']command["']|builtin\s+command)\s. That pattern has no hits today and catches M9–M11.
  • R2, :227-228. "print every line this file keys on alike" holds only for today's keys. Bash 3.2.57 does not expand an alias after a leading redirection, and 5.2 does, so on a Mac's own bash the scan misses M8, which CI catches. One clause fixes it.
  • R3, the PR body and db3aec2's message. "assembly-0001 still applies (git apply --check)" is true of its AGENTS.md hunks. The whole patch stops at .gitattributes, at main too, so this PR did not cause it. Lane 1's D6c re-check covers the whole patch after the landing.
  • Lane 1's NITs 2 and 3 (the exact-shebang key, and declare -f print forms under a newer bash) stand too. Its NIT 4 is R1.

About CI after the landing. Since 04:33Z today, ubuntu-latest is Ubuntu 26.04. There, the package job's Python 3.9 cannot be set up, and one kill-timing test fails (#228, lane 2's 6314613685).

  • Your queue's record is its green prefix runs, so it lands as tested.
  • Main's push runs after each landing will be red until lane 3's runner pin lands. That is expected, and not the queue's doing.

The full review is on Eagle, at /workspace/projects/.lane-worktrees/openRepoTools-3/briefs/rv-294-work/REPORT-294-reply-20261021T1124Z.md.

Lane: openRepoTools-3

@brettheap
brettheap merged commit acc89ea into main Oct 21, 2026
10 checks passed
@brettheap
brettheap deleted the fix/bash32-command-errexit-292 branch October 21, 2026 11:30
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 21, 2026
…scripts, each tool pinned by version and sha256, in a workflow of its own (#277)

This completes the WIP commit b1c59e6, which added lint.yml alone. It is a
branch for v1.2.0, with no PR and no label, and the job has not yet run on a
GitHub runner: every result below was taken on one machine with the pinned
tools, so the CI record is still to come.

No job ran either tool, so a warning on a workflow or a shell script showed
nowhere. Lane openRepoTools-2's review of #228's release.yml found three
mutants that actionlint's shellcheck rule flags and the suite lets through
(I4, I13 and N3), and nothing in CI would have said so.

What changed
- .github/workflows/lint.yml, one job `lint`, in a file of its own. tests.yml
  is the release gate's file: release.yml requires exactly its four jobs,
  tests/test_tests_workflow.py holds that, and rehearsal D rewrites it for the
  code leg. The job runs on pull requests, on main and release/v*, and by
  dispatch, and it is no part of the gate. It runs on ubuntu-24.04, as every
  other ubuntu job does since #296 (ubuntu-latest became Ubuntu 26.04 on
  2026-10-21).
- actionlint 1.7.12 and shellcheck 0.11.0, each a release archive checked with
  sha256sum --strict -c before it is unpacked. Each digest was computed from
  the downloaded archive and equals GitHub's asset digest, and actionlint's
  also equals upstream's checksums.txt. Neither tool comes from the runner
  image or from a package index, and both are static binaries.
- actionlint runs over every workflow with the pinned shellcheck given by
  path, with no exclusion, no severity floor and no config file, and the step
  fails if actionlint reports its shellcheck rule disabled, which it does
  silently when it cannot start the binary.
- shellcheck runs, one file at a time, on every tracked file whose first line
  is a bash or sh shebang, at the baseline below. That is 16 files on main,
  and 15 of them run.
- tests/test_lint_workflow.py (17 tests, 21 cases) pins the file and runs its
  three steps under fakes instead of only reading them: the install step over
  a genuine archive, over a tampered archive of each tool (nothing is
  unpacked, and after a bad actionlint archive the shellcheck one is never
  asked for) and over a tool that says another version; the actionlint step
  under a fake actionlint (a finding, a disabled shellcheck rule, a
  shellcheck that is not the pinned one); the shellcheck step in a throwaway
  git repository (selection by shebang, the baseline's flags, every file
  checked after one fails, an empty selection fails). The header's table of
  codes is held to the step's list, and the baselined scripts are held to be
  tracked bash files.

What the tools found on main a42946e
- release.yml, tests.yml and lint.yml: nothing, at any severity.
- The 16 shell scripts: no error. park, resume and status: nothing at all.
  The other twelve: 319 findings, 190 warnings and 129 notes.
  Warnings: SC2034 157, SC2088 14, SC2209 4, SC1007 4, SC2154 3, SC2069 2,
  SC2178 2, SC2128 2, SC2141 1, SC2318 1. Notes: SC2016 55, SC2018 18,
  SC2019 18, SC2295 11, SC2329 10, SC2086 5, SC2015 5, SC2094 4, SC2020 1,
  SC2012 1, SC2006 1. lanes-edit.sh holds 142 of the 319.
- tests/test_lane_helpers.sh, which the job does not run: 43 warnings and 218
  notes, and 16.2 GB of memory to find them.
- Two of the warnings are latent bugs and not style: SC2141 in
  lanes-edit.sh's session_start_block (IFS='\t' is a backslash and a t, so a
  live-fork row id<TAB>4242<TAB>interactive reads as id<TAB>4242<TAB>in,
  erac and ive) and SC2318 in openRepoTools's wip_fetch_template_file (bash
  expands $rel before the local that sets it takes effect, so path carries
  the caller's rel, which is right only because wip_init calls it from a loop
  over its own rel). Neither is fixed here.

Main has moved since, and the baseline holds on it. On main 50d05d4 (after
#263, #289, #294, #296 and #293) the job's three steps, run as written over
that tree, exit 0: actionlint finds nothing in release.yml, tests.yml and
lint.yml, and shellcheck finds the same 190 warnings, in the same ten codes
and the same counts, no error, and 132 notes (lanes-edit.sh gained two SC2016
and one SC2015). lanes-edit.sh peaks at 10.9 GB and takes about 90 to 130 s,
which a 16 GB runner holds.

What the baseline leaves out
- The workflows: nothing.
- park, resume, status and every script added later: nothing.
- The other twelve scripts are held to --severity=warning less ten codes, so
  the 129 notes and the 190 warnings of SC1007, SC2034, SC2069, SC2088,
  SC2128, SC2141, SC2154, SC2178, SC2209 and SC2318 are left out, and any
  error or warning of another code fails. Each code, its count and the reason
  it is left out are in lint.yml's header.
- tests/test_lane_helpers.sh is not run: it is the lane helpers' own suite,
  nothing ships it, and shellcheck needs 16.2 GB for it (measured), more than
  a 16 GB runner has. lanes-edit.sh is run: 10.5 GB and about two minutes.

The three mutants, applied to release.yml in scratch copies (lane 2's own
diffs), and run through the job's own actionlint step, on a42946e's file and
again on 50d05d4's
- I4 (3.2.*) made *)): SC2221 and SC2222, warning.
- I13 (the link fingerprint check made `if false`): SC2034, warning.
- N3 (exit 0 as the clean step's first line): SC2317, info.
Each makes the step exit 1. N3 is only info, which is why the workflows have
no severity floor.

Tests, run only through tests/run.sh -k
- -k 'test_lint_yml or test_no_committed_file_names_a_host_absolute_path or
  test_adoption_act_zero_is_cited_by_the_sha_that_landed': 23 passed. The
  last two scan every tracked file, so they read lint.yml and the new test.
- Before the workflow existed (lint.yml set aside), -k test_lint_yml: 19
  errors and 1 failure of its 21 cases. The 21st reads only tracked files and
  passed.

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

ready landing gate: runs tests-macos once before the squash

Projects

None yet

1 participant