Skip to content

fix(scripts): make the R7a bash-3.2 control hold on a host that IS bash 3.2 - #12298

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-12254-r7a-control-on-bash32-host
Aug 25, 2026
Merged

fix(scripts): make the R7a bash-3.2 control hold on a host that IS bash 3.2#12298
yinlianghui merged 1 commit into
mainfrom
claude/issue-12254-r7a-control-on-bash32-host

Conversation

@yinlianghui

@yinlianghuiyinlianghui commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes#12254

What was red

pnpm check:objectui-changeset exits 1 on every stock macOS seat — not on the rule it
gates, but on the R7a control leg#12071 added. R7a proved the BASH_ENV harness was
real by running a mapfile probe plain and simulated and requiring that only the
simulated one fail. On stock macOS /bin/bash is 3.2, so mapfile is missing from the
plain run too: plain= came back empty exactly like sim.out=, and "the simulation
removed something" could not be satisfied by any correct harness.

I read the code before trusting the card, and the card's account is exactly right
scripts/objectui-changeset-digest.mjs really did probe mapfile itself, both ways, and
assert probePlain.stdout.includes('MAPFILE-WORKS').

A control written to stop a vacuous pass had become a guaranteed fail on one platform
— the same error family one level up: a check whose own precondition was never measured
across the population of hosts it runs on.

The option search — B, and the builtin is chosen by the host

The card leaves the choice open and conditions the preference: "B is the better shape if
a suitable builtin exists."
So I searched rather than assumed. Across every bash builtin
with no external /usr/bin twin, enable -n NAME produces exactly the status-127
command not found that a missing bash-4 builtin produces on 3.2 — measured here:

shopt after enable -n: st=127 | bash: shopt: command not found
caller after enable -n: st=127 | bash: caller: command not found
compgen after enable -n: st=127 | bash: compgen: command not found
hash after enable -n: st=127 | bash: hash: command not found
umask after enable -n: st=127 | bash: umask: command not found
printf after enable -n: st=1 | (falls through to /usr/bin/printf — disqualified)
echo/test after enable -n: st=0/1 | (external twin — disqualified)

So option B is available. But naming one builtin and asserting it exists on bash 3.2
would repeat the precise mistake this card is about — a hardcoded belief about another
platform's builtin table, held by someone who cannot run that platform. So the fix does
not hardcode one. It asks the host: probeBuiltin is selected at runtime from the
candidates above, keeping the first the shell itself reports as a builtin (type -t ==
builtin) with no external twin (type -P empty). On this container it picks shopt.

The leg is then unconditional and measures the harness, not the platform.

Not a skip. A host offering no qualifying builtin reddens loudly. Option A's
SKIPPED: leg was not needed and is not present — swapping one vacuity for another was
never on the table.

A second leg for the state R7b actually depends on

R7a2 asserts that in the shell R7b runs, mapfile is unavailable. That holds on both
hosts for different reasons — enable -n removed it on bash 5, it was never there on
bash 3.2 — so it is its own leg rather than folded into R7a. Neither leg alone is
unconditional; together they leave no host on which R7b can pass vacuously. The detail line
names which way the host got there, so a macOS operator reading a green run still learns
their /bin/bash is the genuine 3.2 article.

⚠️ What could NOT be verified from here — read this before accepting

This container is Linux with bash 5.2.21. Nobody on this lane can stand on the platform
where the bug reproduces, and this PR does not claim to.
No run below happened on macOS.

What was done instead: the failing condition was constructed locally — a BASH_ENV
file that disables mapfile/readarray makes every bash this self-test spawns genuinely
lack the builtin, which is the mechanism the macOS failure runs on. On origin/main that
reproduces the reported symptom byte-for-byte:

$ BASH_ENV=host-is-32.sh node scripts/objectui-changeset-digest.mjs --self-test
✗ #12071 R7a the simulated-3.2 harness really removes the builtin (else R7b proves
nothing) — plain= sim.out= sim.err=/tmp/.../mapfile-probe.sh: line 1: mapfile:
command not found
⛔ objectui-changeset-digest --self-test: 1 failure(s) EXIT=1

Same tree, after this change: EXIT=0.

Still unverified from this seat, stated plainly rather than papered over:

  • A real macOS bash 3.2.57 run. The construction models the mechanism (builtin
    absent), not the platform. A 3.2-specific difference in type -t / type -P /
    enable -n would not be visible here. The runtime probe selection is what makes that gap
    survivable: the host answers for itself, and if shopt were somehow not a builtin there,
    the next candidate is taken and a host with none reddens rather than lying.
  • Direct evidence that shopt / caller / compgen ship in bash 3.2. Outbound fetches
    of the bash sources are blocked from this container (git.savannah.gnu.org → proxy 403;
    bminor/bash → not in this session's allowed repo set). This PR therefore does not
    rest on that claim — which is the point of selecting at runtime.

Acceptance

(a) The gate goes green where it was red — demonstrated on the constructed
builtin-less shell, not claimed for macOS:

runorigin/mainthis branch
plain host (bash 5)EXIT=0EXIT=0
constructed builtin-less hostEXIT=1, 1 failure, R7aEXIT=0, all checks passed

(b) R7b still fails if bump-objectui.sh reintroduces a bash-4 builtin.
scripts/bump-objectui.sh is untouched by this PR. Mutation: a mapfile line injected
after set -euo pipefail, confirmed on disk by grepping for the injected marker (an
editor's exit code is not evidence — perl -i returns 0 on zero matches):

✗ #12071 R7 bump-objectui.sh names no bash 4+/5 construct — 89: mapfile -t _os_bash4_regression …
✓ #12071/#12254 R7a the BASH_ENV harness really removes a builtin THIS host has
✓ #12071/#12254 R7a2 … and in the R7b shell mapfile is gone
✗ #12071 R7b on a shell without bash 4 builtins the #10495 warning still fires — status=127
✗ #12071 R7c … exit 0, pin written, verdict on stdout — status=127
⛔ 3 failure(s) EXIT=1

Restored and proven byte-identical: git hash-object = git rev-parse HEAD: =
c1ff2049c4e18585dc85d40ad4e7369a1c7ce3b9, git status --porcelain empty.

⚠️ One nuance the card could not have known, found by running it: on a genuinely
builtin-less host the same mutation aborts the self-test earlier than R7b — an uncaught
execFileSync throw at digest line 1516, since every leg there already runs on a
mapfile-less shell. Still a loud red naming mapfile: command not found, but not R7b's
assertion. Verified pre-existing by replaying the mutation against origin/main's own
digest: identical abort, same line. Not introduced here.

The new control is itself non-vacuous — proven by mutation

Two mutations, each confirmed on disk before it was read, each restored under an EXIT
trap:

  1. Neuter the probe's disabling file (models "BASH_ENV silently ignored"): R7a reddens
    on both host shapes — builtin=shopt plain=PROBE-STATUS=0 sim=PROBE-STATUS=0. This
    is the property the old leg could not have: it was red on the 3.2-shaped host whether
    the harness worked or not.
  2. Neuter R7b's own simulation file: R7a2 reddens —
    host-has-mapfile=true … sim.out=MAPFILE-WORKS — while R7b passes. That is the
    vacuous pass R7a exists to prevent, caught.

Two things found while verifying, reported not fixed

  • The blast-radius claim is true and understated.check:objectui-changeset is a step
    in .github/workflows/lint.yml, which declares no paths: filter — it runs on every
    PR to main. And dispatch-gates.mjs reaches it through pendingChangesetFamilies, the
    changeset-directory probe, so every card that writes a changeset derives it. Not "a
    broad set of paths": effectively every card in the repo.
  • ⚠️This defect class is invisible to CI by construction. CI is ubuntu-latest with
    bash 5; the failure exists only where /bin/bash is 3.2. Nothing in the pipeline will
    ever catch the next one, which is why this sat red locally on every macOS seat with every
    PR reporting green. Not fixed here — out of scope for this card, and A repo-wide check: for bash-4-only constructs in tracked *.sh — measured 4-for-4, but it needs a new gate file plus wiring #12221 (a repo-wide
    bash-4 gate) is the adjacent lane. Recorded so the gap is on the record.

Related and deliberately not folded in: #12221 and #12222 are separate cards.

Verification

Gate union derived at the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no STALE TREE
warning; change set = 1 path, three-dot semantics). All 9 matched families run at
10ddefd88a, exit codes captured before any pipe, each gate's own verdict line quoted:

agent-test-spelling EXIT=0 | ✓ check-agent-test-spelling: 0 violations — 381 file(s) …
cli-command-ids EXIT=0 | ✓ check-cli-command-ids: 276 command-id literal(s) … all resolve
cross-package-test-inputs EXIT=0 | OK: 16 package(s) read outside themselves, all declared …
entry-guard EXIT=0 | ✓ check:entry-guard: 165 scripts/ file(s) …
objectui-changeset EXIT=0 | ✓ objectui-range --self-test: all checks passed
parse-guard EXIT=0 | ✓ check:parse-guard: 164 scripts/ file(s) …
pnpm-filter-targets EXIT=0 | ✓ check:pnpm-filter-targets: 136/173 `--filter` occurrence(s) …
ci-filter-parity EXIT=0 | OK: all 96 declared cross-package glob(s) (81 unique) …
cross-pkg-direct EXIT=0 | OK: 16 package(s) read outside themselves, all declared …

check-ci-filter-parity first exited 1 with PREREQUISITE NOT MET — the dependency 'yaml' is not installed; its own text says "Nothing was measured … It is NOT a finding."
pnpm install, then green. Working tree clean at that commit.

Declared narrowing: the 9 families above were run directly rather than through
scripts/pm/os-verify-lock.sh. They are static node gates — no build, no test suite — and
the lock was held by another agent's build (391s held, one waiter) at the time. No
repo-wide pnpm lint sweep was run; CI runs the farm exactly once regardless.

Changeset: none. This is a gate self-test with no user-visible surface — skip-changeset.

Generated by Claude Code

…sh 3.2
R7a probed `mapfile` itself to prove the BASH_ENV harness really removes a
builtin. On stock macOS `/bin/bash` is 3.2, so `mapfile` is absent from the
PLAIN run too — `plain=` came back empty exactly like `sim.out=`, the assertion
could not be satisfied by any correct harness, and `check:objectui-changeset`
sat red on every macOS seat while CI (ubuntu-latest, bash 5) stayed green.
Option B: the probe builtin is now chosen at RUNTIME from bash 2.x-era builtins
the host itself reports as builtins and that have no external `/usr/bin` twin,
so `enable -n` on one yields the same 127 "command not found" a missing bash-4
builtin yields on 3.2. No skip leg: a host offering no qualifying builtin
reddens loudly. A second leg, R7a2, asserts the state R7b actually depends on —
mapfile unavailable in the R7b shell — which holds on both hosts for different
reasons, so neither host can pass R7b vacuously.
`scripts/bump-objectui.sh` is untouched; R7/R7b/R7c still redden when a bash-4
builtin is reintroduced into it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
@yinlianghuiyinlianghui added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 25, 2026 — with Claude
@yinlianghui
yinlianghui marked this pull request as ready for review August 25, 2026 17:11
@yinlianghui
yinlianghui added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit 91066beAug 25, 2026
40 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-12254-r7a-control-on-bash32-host branch August 25, 2026 17:29
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] check:objectui-changeset is red on every macOS seat — R7a's bash-3.2 simulation control cannot pass on a host that IS bash 3.2

2 participants

@yinlianghui@claude