Found while fixing #7260 (the guard-main-checkout.sh path-key defect). Out of scope for that
card, which is confined to two hook files. Filed rather than fixed.
The gap
The sibling repo runs a gate that holds every tracked shell file to a bash 3.2 floor:
✓ check-bash32-floor: 27 tracked shell file(s) under scripts/**, .claude/hooks/**, .githooks/**
name no bash 4+ construct outside a comment, a guarded ${VAR:-} read, or a non-command position.
census: 25 by .sh extension, 2 by shebang alone; 19 constructs checked, floor bash 3.2.
This repo has no equivalent. Reading the check:* scripts in package.json and
grep -l '\.claude' scripts/*.mjs, the two that come closest are neither:
check:control-bytes scans tracked text files for control bytes, and
check:shell-escape-residue scans markdown fences in AGENTS.md, CLAUDE.md, skills,
.claude/skills and content/docs — its own verdict line reports "206 file(s) and 1307
fenced block(s)", i.e. documentation, not shell programs.
Why #7263 does not cover it
#7263 (upstream-port-pin.json covers only scripts/, so the .claude/hooks/** verbatim
ports have no drift gate) covers the ported half and, if fixed, would cover it well: every
one of this repo's eight .claude/hooks/*.sh files has a same-named counterpart in the
sibling repo, so pinning them to that repo inherits its bash floor transitively. Measured,
not assumed — the two hook directories were listed and compared, and the set difference is
empty.
What neither card reaches is this repo's own shell, which is not a port of anything:
e2e/live/ci/start-backend.sh
e2e/live/ci/stop-backend.sh
scripts/ensure-chromium-ready.sh
scripts/setup.sh
All four are in the sibling gate's declared scan roots (scripts/**, and e2e/** would need
adding), so in that repo they would be held to the floor. Here nothing holds them.
Severity: a ratchet, not a live defect — with a bounded reading
Grepping those four files for ten of the constructs the sibling gate names (declare -A,
local -A, ${var^^}, ${var,,}, mapfile, readarray, &>>, |&, globstar,
[[ -v , coproc) returns no match. So there is no violation to fix today.
⚠️ That reading is bounded and should not be quoted as a clean bill: the sibling gate checks
19 constructs and this grep covered ten of them, so "clean today" here means "clean under
a partial scan". The point of the card is the missing ratchet, not a present breakage —
scripts/setup.sh runs on contributor machines where /bin/bash is still 3.2 (macOS), which
is exactly the host the floor exists for, and it is the one file of the four whose shebang is
#!/bin/bash rather than #!/usr/bin/env bash.
Shape of the fix (not asserted)
Port scripts/check-bash32-floor.mjs from the sibling repo with this repo's scan roots
(scripts/**, .claude/hooks/**, e2e/**), wire it into the check:* scripts and the lint
workflow. The gate ships with its own self-test (153 cases in the sibling), so the port has a
red/green of its own.
Filed unassigned, finding.
Generated by Claude Code
Found while fixing #7260 (the
guard-main-checkout.shpath-key defect). Out of scope for thatcard, which is confined to two hook files. Filed rather than fixed.
The gap
The sibling repo runs a gate that holds every tracked shell file to a bash 3.2 floor:
This repo has no equivalent. Reading the
check:*scripts inpackage.jsonandgrep -l '\.claude' scripts/*.mjs, the two that come closest are neither:check:control-bytesscans tracked text files for control bytes, andcheck:shell-escape-residuescans markdown fences inAGENTS.md,CLAUDE.md,skills,.claude/skillsandcontent/docs— its own verdict line reports "206 file(s) and 1307fenced block(s)", i.e. documentation, not shell programs.
Why #7263 does not cover it
#7263 (
upstream-port-pin.jsoncovers onlyscripts/, so the.claude/hooks/**verbatimports have no drift gate) covers the ported half and, if fixed, would cover it well: every
one of this repo's eight
.claude/hooks/*.shfiles has a same-named counterpart in thesibling repo, so pinning them to that repo inherits its bash floor transitively. Measured,
not assumed — the two hook directories were listed and compared, and the set difference is
empty.
What neither card reaches is this repo's own shell, which is not a port of anything:
All four are in the sibling gate's declared scan roots (
scripts/**, ande2e/**would needadding), so in that repo they would be held to the floor. Here nothing holds them.
Severity: a ratchet, not a live defect — with a bounded reading
Grepping those four files for ten of the constructs the sibling gate names (
declare -A,local -A,${var^^},${var,,},mapfile,readarray,&>>,|&,globstar,[[ -v,coproc) returns no match. So there is no violation to fix today.19 constructs and this grep covered ten of them, so "clean today" here means "clean under
a partial scan". The point of the card is the missing ratchet, not a present breakage —
scripts/setup.shruns on contributor machines where/bin/bashis still 3.2 (macOS), whichis exactly the host the floor exists for, and it is the one file of the four whose shebang is
#!/bin/bashrather than#!/usr/bin/env bash.Shape of the fix (not asserted)
Port
scripts/check-bash32-floor.mjsfrom the sibling repo with this repo's scan roots(
scripts/**,.claude/hooks/**,e2e/**), wire it into thecheck:*scripts and the lintworkflow. The gate ships with its own self-test (153 cases in the sibling), so the port has a
red/green of its own.
Filed unassigned,
finding.Generated by Claude Code