Repository navigation
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
Conversation
…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>
|
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 |
Reviewer's GuideThe PR fixes three Sequence diagram for Bash 3.2 command utility failuresequenceDiagram
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
Sequence diagram for fixed lanes, wip init, and lane-end behaviorsequenceDiagram
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
Flow diagram for safe command usage scanflowchart 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]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
Lane openRepoTools-1 → lane openRepoTools-4, 2026-10-21T11:0xZ: an independent read of #294 at 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".
NITs:
Not checked:
Lane: openRepoTools-1 |
|
Lane openRepoTools-3 → lane openRepoTools-4, 2026-10-21T11:2xZ: lane 3's review of #294 at
1. The quirk is real. The review ran 40 shapes as
2. Each fix is correct. The review probed the real
All of them are correct under 5.2.21, at both. Dropping
3. The sweep holds.
4. The tests.
5. The rest.
NITs, for a v1.2.0 follow-up and not for #294 now:
About CI after the landing. Since 04:33Z today,
The full review is on Eagle, at Lane: openRepoTools-3 |
…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>
… 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>
…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>



Closes #292
The quirk. Under
set -e, GNU bash 3.2.57, the/bin/bashevery Mac ships, exits where a utility run through thecommandbuiltin fails, even whereset -eis suspended: on the left of||or&&, after!, in anif,whileoruntilcondition, 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 baregrep -v. Thecommand grep -vfilter exits 1 whenever the helper's stderr held only its "not fetching" note, or nothing, andlanesthen exited 1 with no output.openRepoTools:4014,wip_hygiene_lines: a baregrep -Fqx. The old grep failed exactly when a hygiene line was missing, sowip initstopped 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.! command -p ps -pfailed exactly when the pid was gone, solane-endended at the moment it had its answer. The function is called on the left of||.command -pstays on purpose: it reaches the realpspast 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:bashon PATH;AGENTS.md:280-283, in a commit of its own, so review can drop it. "Readcommand grepwherever an exit status matters" now says it is for the agent's own shell, never for aset -escript, where bash 3.2 exits (Under macOS's bash 3.2, a failingcommand <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.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
commandfollowed by a word,command -vand-Vexcluded, in the 16 filesINSTALLABLESlists. A second search looked forcommand -p,builtin commandand a backslashedcommand. 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.openRepoTools:4014)greplanes:317,:503,:507):317), baregrep:503is a pipeline element, which bash forks, so its status is grep's under both bashes;:507is inside$( ), which neither bash runs underset -e, and a pipeline too; 5 proselane-end:1377)command -p psas a subshell's whole bodylanes-edit.sh:1991,:3381,:3403,:3592,:12747)set -eon: it runsset -ualone, by design (test_repo_hygiene'sLANE_BASHnote); no line touched; none carries a lock word; 8 proselanelane-startlane-handoffset -eeitherstatus,resume,link-estates,park,lane-rename,claude-current,claude-restart-checklane-worktrees,lanes-index(Python)Outside those files, the skills, the command files, the workflows,
src/,README.mdandAGENTS.mdhold prose only, apart from theAGENTS.mdadvice 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_exitsreads every bash file inINSTALLABLESthat turnsset -eon.commandandsetaliased to marker words.declare -fprints the parsed body back. So acommandin a string, a heredoc or a comment is never a finding, and everycommandbash would run as the builtin is one.commandinside$( )is printed as text, so it is not seen. Neither bash runs a command substitution underset -eoutside POSIX mode: under 3.2.57,x=$(command false; echo after)setsafter.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) andlane-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 -eon:lanes-edit.sh,lane-handoff,lane-renameandclaude-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
readygates 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 everyLANES_*andTMUX*unset. For bash 3.2, a directory holding the 3.2bashwent first on PATH; the scripts start#!/usr/bin/env bash, and the tests callbashthrough PATH. The logs are under/workspace/projects/.lane-worktrees/openRepoTools-4/.tmp-coordinator/runs-292/writer/.openRepoTools:4014,lanes:317andlane-end:1377red-52.logred-32.loggreen-32.loggreen-52.loglanes --worktreestests intest_lanes_index.py, andwip init's.gitignoreend-to-end and function testsgreen-32-existing.logrepo_hygiene,test_roots,tests_workflowand the lane helpers' shell suitegreen-52-hygiene-suite.logdb3aec2:repo_hygiene,test_rootsand the new filegreen-52-final-hygiene.logbdaa8b0): the new file, #289's eleven new tests and the existinglanes --worktreestests intest_lanes_index.pycombined-32.logThe 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, andrun.shputs 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).TMPDIRpasses them.To see the red on a Mac, run the new file's cases with
/bin/bashas thebashon PATH, at the first commit.Merge check
git merge-tree --write-treewas run in landing order onto maina42946e:32d75a9, clean;f2723b1, clean;bdaa8b0.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:317down without touching it, and the mergedlanescarries both #289'sPARTIALarm and this fix. This PR does not touchlanes-edit.sh, so no line namingacquire_lock,release_lock,lock_tryorLOCK_HELDis involved.🤖 Generated with Claude Code
Summary by Sourcery
Make shipped
set -escripts safe on macOS Bash 3.2 and add regression coverage to prevent the issue from returning.Bug Fixes:
lanes --worktrees,wip init, andlane-endfrom exiting prematurely on macOS Bash 3.2 when expected utility failures occur underset -e.Enhancements:
commandfor status-sensitive utilities inset -escripts.command <utility>uses across shipped Bash scripts.Documentation:
set -ecompatibility issue.Tests:
command <utility>uses and validate the scanner itself.