fix(scripts): drop the bash 4 mapfile/readarray builtins from the three remaining hand-run scripts - #12219
Merged
Merged
Conversation
…hand-run scripts with the bash 3.2 read loop macOS ships bash 3.2.57 and `/usr/bin/env bash` resolves to it, so `mapfile`/`readarray` are `command not found` there. All three scripts run under `set -e`, so each aborted at status 127 rather than degrading. scripts/gen-sdui-manifest.sh is the sharp one: `pnpm sdui:manifest` is the literal NEXT STEP that scripts/bump-objectui.sh prints on its way out, and the ratchet is an on-demand gate by decision (#5960) whose only routine CI invocation is cut-rc.yml. So the host it is normally run on is the operator's laptop. Same read-loop idiom as the bump-objectui.sh repair, including the two load-bearing details it documents: `if [[ -n "$x" ]]; then …; fi` rather than a trailing `&&`-list, and an explicit `arr=()` before the loop so `set -u` cannot turn an empty result into `unbound variable`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
This was referenced Aug 25, 2026
yinlianghui
marked this pull request as ready for review
August 25, 2026 14:22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12141
Three hand-run shell scripts still called the bash 4 builtins
mapfile/readarray./usr/bin/env bashis bash 3.2.57 on macOS, all three run underset -e, so on a Mac each aborted at status 127 rather than degrading. Same defect class and same repair idiom as thescripts/bump-objectui.shfix that landed in4b47855c05.scripts/gen-sdui-manifest.sh:585readarray -t DUMP_DEV_ARGVscripts/pm/os-regen-merge.sh:68mapfile -t regen_pathsscripts/publish-smoke.sh:674mapfile -t DEV_ARGVscripts/gen-sdui-manifest.shis the sharp one:pnpm sdui:manifestis the literal→ NEXT STEPthatscripts/bump-objectui.shprints on its way out (verified on the merged tree —scripts/bump-objectui.sh:560-564), so a macOS operator now gets past the pin bump and stopped one command later.No
BASH_VERSINFOversion refusal was added, for the reason the sibling repair recorded: after the rewrite these scripts have no bash-4 dependency, so a version guard would refuse on precisely the host they now support.Verification — a fix that cannot be demonstrated on Linux is not a fix
None of these three has a PR-CI path, so a green CI run says nothing in either direction. Everything below was run on Linux (bash 5.2.21) with the bash 4 builtins taken away by
enable -n mapfile readarraythroughBASH_ENV.The instrument is not vacuous. Measured on a probe first, both ways, so a
BASH_ENVthat stopped being honoured cannot leave the real case passing while proving nothing:scripts/pm/os-regen-merge.sh— a real end-to-end run. Driven inside a synthetic repo (upstream + clone + feature branch +merge=os-regenentries in.gitattributes), so the whole merge sequence executes:os-regen-merge.sh: line 68: mapfile: command not found,HARNESS-EXIT=127— aftergit fetch origin main, before step 1, i.e. with the ordering the script exists to enforce not begun.HARNESS-EXIT=0.merge=os-regenentries — theset -uempty-array branch:✗ no merge=os-regen entries found in .gitattributes — refusing to guess, exit 1. Same verdict and same exit code as the pre-fix bytes on bash 5. This branch is why the loop needs an explicitregen_paths=(); without it the empty read dies atregen_paths: unbound variableinstead of reaching the refusal (measured both ways).scripts/gen-sdui-manifest.shandscripts/publish-smoke.sh— extracted-bytes runs. Both are too heavy to run end to end here (an objectui build tree plus a Playwright browser; a full pack-and-scaffold smoke). So the real bytes of the producer function and of the array-fill block are extracted from the file on disk, composed underset -euo pipefail, and executed:sdui_dev_server_cmd→DUMP_DEV_ARGVreadarray: command not found, exit 127smoke_dev_server_argv→DEV_ARGVmapfile: command not found, exit 127and the resulting argv arrays are byte-identical across pre-fix/post-fix and across plain/simulated shells (
diffclean, 9 and 8 tokens) — the repair changes no behaviour on bash 5 either.What the sweep found that the card did not
*.shcensus is 18, not 17..claude/hooks/guard-main-checkout.selftest.shwas added today, after the card's census. It is clean, but the drift is the point: the population moves daily.scripts/publish-smoke.shis CI-wired, contrary to the card..github/workflows/publish-smoke.yml:225and:260both run it. Itsmapfileline is therefore green on Linux today; what it broke was the hand-run reproduction of a publish-smoke failure on a Mac. Lower stakes than the other two, still a real break.scripts/gen-sdui-manifest.shis not entirely CI-less either..github/workflows/cut-rc.yml:443runspnpm sdui:manifestbefore publish, a handful of times a month. The sdui.manifest.json 的来源未定:声明一致性 ratchet 目前只在手工pnpm sdui:manifest时跑,CI 里从来不跑(#4690 的遗留决定) #5960 ruling keeps it off PR CI, which is what matters here.scripts/pm/os-regen-merge.shreally is CI-less — nothing under.github/workflows/invokes it.coproc,globstar,;;&,wait -n,[[ -v,declare -n/-g,printf %(…)T,${x@Q},lastpipe,${arr[-1]}andexec {fd}on top of the card's construct set: none present.bash -nparses all three clean.&&trap to the empty-list case; measured here, an all-empty read leaves the loop body unexecuted and thewhileat status 0. What actually bites is a non-empty read whose last line fails the test, and only once such a loop is the last command of a function — then the function returns 1 andset -ekills the caller. Theifform is still the right one; the comments added here state the measured mechanism.The generalisation question, measured
check:-ing this class repo-wide was called out as in scope but a judgment call. The numbers at this commit:*.sh; 3 carried a bash-4-only construct before this PR, 0 after; 4 sites total across this card and the sibling one.scripts/pm/os-verify-lock.sh, and every occurrence there is either a guarded${EPOCH*:-}read or lives inside its own self-test that hunts them — clean by construction.The gate is well-earned — a 4-for-4 hit rate on a class that stayed invisible until it bit twice, over a population that gained a member today. But it needs a new
scripts/check-*.mjs, its own--self-test, apackage.jsonalias and twolint.ymlsteps, and it would then joincheck-self-test-wired.mjs's population. That is the "new gate file plus wiring" shape this card was told to report before building, so it is reported, not built here.Gates
Derived at the final commit
b9438a425dwithnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(re-derived after the commit; same 7 families). Exit codes captured before any pipe.Repo-wide
pnpm lintwas narrowed, and the narrowing is measured, not assumed: this diff is three.shfiles, and eslint's own configuration resolution reportsFile ignored because no matching configuration was suppliedfor each of them (--format json: 3 files, 0 errors, 3 ignored-warnings). No eslint program includes a.shfile, so this diff cannot move the verdict on any untouched file.Changeset: none. Root
scripts/only, nothing published changes ⇒skip-changeset.Generated by Claude Code