scripts/check-bash32-floor.mjs carries a construct table of bash-4+ spellings it refuses in this repo's tracked shell. It has an append-both entry for the & before a doubled > redirection (bash 4.0). It has no entry for |&, the pipe-both operator, which arrived in the same bash release and fails the same way on the declared 3.2 floor.
Found incidentally while fixing #12518 — the gate correctly caught an append-both spelling I had written into scripts/pm/os-verify-lock.sh's self-test, and the neighbouring pipe-both spelling in the very same block sailed through. Unrelated to that card's surface, so filed rather than fixed.
Measured
The table's ids, read off the source:
$ grep -n "id:" scripts/check-bash32-floor.mjs
189: mapfile 211: nameref-global 233: wait-n 257: fd-autoalloc
200: assoc-array 222: coproc 244: shopt-4 268: case-modify
279: param-transform 290: negative-subscript 301: case-fallthrough
312: has-v 323: printf-time 334: append-both 345: epoch-vars
Fifteen constructs, append-both among them, no pipe-both. The gate's own green line confirms the count: "15 constructs checked, floor bash 3.2".
Both operators are bash 4.0 (|& is listed in the bash 4.0 NEWS as a new operator; the gate already dates append-both to 4.0 in its own message). On bash 3.2:
cmd &>> log parses as & then >> — the command is backgrounded. The gate says exactly this, and it is why the entry exists.cmd |& next is a syntax error — the script does not run at all.
The second is arguably the louder failure of the two, and it is the one nothing checks.
Why it matters here rather than in the abstract
The gate's value is that it fires on a host nobody develops on: "Your local run and CI both pass because both run bash 5. That is the point of this gate." A gap in the table is therefore invisible by construction — no local run and no CI run can reveal it, which is the same reason the class survived long enough to need the gate in the first place (#12221).
It also fires on quoted occurrences, not only command position — measured on the first draft of the #12518 change, where the flagged lines were an st_case label and a single-quoted payload string handed to bash -c. So the coverage asymmetry is not narrow: any tracked shell file mentioning either operator is judged by whether the table happens to know it.
Suggested shape (not prescribing)
A pipe-both entry alongside append-both, with the same self-test pair the table's other entries carry (the pattern matches a real use; the pattern does NOT match its 3.2 replacement 2>&1 |). Worth a sweep of the remaining bash 4.0 operator set at the same time rather than one entry at a time — the table is a denylist, so anything absent reads as approved.
Filed unassigned for triage; no fix attempted here.
Generated by Claude Code
scripts/check-bash32-floor.mjscarries a construct table of bash-4+ spellings it refuses in this repo's tracked shell. It has anappend-bothentry for the&before a doubled>redirection (bash 4.0). It has no entry for|&, the pipe-both operator, which arrived in the same bash release and fails the same way on the declared 3.2 floor.Found incidentally while fixing #12518 — the gate correctly caught an append-both spelling I had written into
scripts/pm/os-verify-lock.sh's self-test, and the neighbouring pipe-both spelling in the very same block sailed through. Unrelated to that card's surface, so filed rather than fixed.Measured
The table's ids, read off the source:
Fifteen constructs,
append-bothamong them, no pipe-both. The gate's own green line confirms the count: "15 constructs checked, floor bash 3.2".Both operators are bash 4.0 (
|&is listed in the bash 4.0 NEWS as a new operator; the gate already datesappend-bothto 4.0 in its own message). On bash 3.2:cmd &>> logparses as&then>>— the command is backgrounded. The gate says exactly this, and it is why the entry exists.cmd |& nextis a syntax error — the script does not run at all.The second is arguably the louder failure of the two, and it is the one nothing checks.
Why it matters here rather than in the abstract
The gate's value is that it fires on a host nobody develops on: "Your local run and CI both pass because both run bash 5. That is the point of this gate." A gap in the table is therefore invisible by construction — no local run and no CI run can reveal it, which is the same reason the class survived long enough to need the gate in the first place (#12221).
It also fires on quoted occurrences, not only command position — measured on the first draft of the #12518 change, where the flagged lines were an
st_caselabel and a single-quoted payload string handed tobash -c. So the coverage asymmetry is not narrow: any tracked shell file mentioning either operator is judged by whether the table happens to know it.Suggested shape (not prescribing)
A
pipe-bothentry alongsideappend-both, with the same self-test pair the table's other entries carry (the pattern matches a real use; the pattern does NOT match its 3.2 replacement2>&1 |). Worth a sweep of the remaining bash 4.0 operator set at the same time rather than one entry at a time — the table is a denylist, so anything absent reads as approved.Filed unassigned for triage; no fix attempted here.
Generated by Claude Code