fix(install): layer-units red on macOS bash 3.2 — guard array expansions + un-export test scope arrays - #989
Conversation
…ons + un-export test scope arrays Two independent bash-3.2-only defects, latent since the S1 modularization (1c08b16, #719) — surfaced 2026-07-11 by the first full local run of tests/install-sh on macOS. CI never saw them: ubuntu bash >= 4.4 expands empty/unset arrays under `set -u` without error; macOS /bin/bash 3.2.57 aborts with "unbound variable". 1. Layers: setup.d/20-agents.sh:56 `"${SHIPPED_DOCS[@]}"` and setup.d/99-finalize.sh:383+386 `"${DEVDEPS[*]}"` / `"${RUNTIME_DEPS[*]}"` were unguarded — crashing under set -u on bash 3.2 whenever the array is empty or unset, violating the repo's own bash-3.2-safety convention (setup.d/lib.sh:281 comment; setup.d/60-ci.sh:156 idiom). Fixed with the established guards: `${arr[@]+"${arr[@]}"}` loop guard + `${arr[*]-}` default-empty. Output is byte-identical when arrays are populated (byte-identical gate: 8/8 combinations unchanged). 2. Test harness: layer-units.test.sh `_setup_dispatcher_scope` used `export SHIPPED_DOCS=()` (+ SKIPPED, DEVDEPS). On bash 3.2 an export+array assignment inside a function is silently function-local — the arrays were UNSET (not empty) in the layer-sourcing subshell, so even the documented-safe `${#SKIPPED[@]}` length check at 99-finalize.sh:344 crashed. Arrays now use plain assignment: bash cannot export arrays at all, and layers are sourced into the same shell, so export was inert on bash >= 4.4 and harmful on 3.2. Real consumer installs were never affected: install.sh:103 (SKIPPED), install.sh:129 (SHIPPED_DOCS), setup.d/70-deps.sh:209/226 (DEVDEPS/RUNTIME_DEPS) initialize all four arrays non-empty at top level before the affected expansions run. Evidence: layer-units PASS=25 FAIL=2 (suite exit 1) -> PASS=27 FAIL=0 (exit 0) on bash 3.2.57; PASS=27 FAIL=0 on bash 5.3.9 (linux container, CI parity); byte-identical 8/8 green; shellcheck clean; f8-agents-scripts-shipped PASS=8 FAIL=0.
|
Follow-up verification (2026-07-11): both Observations WITHDRAWN as false positives.
No follow-up PR needed. |
…at pre-push (#990) * fix(drift-gate): shipped-eslint-rules --check detects orphans + runs at pre-push Two deviations from the drift-gate invariant (cold backward-sweep, 2026-07-11, python-delivery-v0 S1): 1. No orphan detection — the check loop iterated only from .ts sources, so deleting a rule source left its committed .mjs/.d.ts silently shipping. Added an orphan walk (committed artifact without a matching shippable source = RED), mirroring the python-template drift gate's committed-artifacts-without-source walk. 2. CI-only channel — the gate ran solely in audit-self.yml, one channel later than its pre-push siblings (synth-bundle --check, manifest render). Wired as pre-push section 3g (exit 2 = tsc absent → degrade to warn-skip, mirroring 3f's esbuild skip). RED-then-GREEN evidence: with require-error-boundary.ts moved aside, --check emits ORPHAN for both .mjs and .d.ts and exits 1; restored, exits 0 (24 checks). Verified under macOS bash 3.2 (#989 precedent) and npm --prefix packages/core run test:hooks (716/716 green). Prior-art: skipped — drift-gate hardening of an existing script + pre-push wiring; no new dependency, module, or capability * test(install-sh): regen byte-identical baselines — pre-push.ts hash shifted by section 3g Only delta across all 8 fingerprints is the packages/core/hooks/pre-push.ts sha (shipped file). SNAPSHOT_MODE=capture + local compare 8/8 green. Prior-art: skipped — snapshot baseline regeneration after shipped-file edit, no new capability --------- Co-authored-by: t <t@t.co>
Summary
tests/install-sh/layer-units.test.shwas red on staging HEAD when run locally on macOS — units20-agents.shand99-finalize.shfailed to source in the minimal dispatcher scope. Diagnosis: not a recent regression — two independent bash-3.2-only defects, latent since the S1 modularization (1c08b1627, #719), verified by re-running the probe against the S1-era file versions (identical failures). CI never saw them because ubuntu bash ≥ 4.4 expands empty/unset arrays underset -uwithout error; macOS/bin/bash3.2.57 aborts withunbound variable."${SHIPPED_DOCS[@]}", setup.d/99-finalize.sh:383+386"${DEVDEPS[*]}"/"${RUNTIME_DEPS[*]}"— violating the repo's own bash-3.2-safety convention (setup.d/lib.sh:281 comment; setup.d/60-ci.sh:156 idiom)._setup_dispatcher_scopeusedexport SHIPPED_DOCS=()(+SKIPPED,DEVDEPS). On bash 3.2 an export+array assignment inside a function is silently function-local, so the arrays were UNSET (not empty) in the layer-sourcing subshell — even the documented-safe${#SKIPPED[@]}length check at setup.d/99-finalize.sh:344 crashed.Both sides were wrong, so both are fixed: layers get the established guards (
${arr[@]+"${arr[@]}"}loop guard,${arr[*]-}default-empty); the test scope assigns arrays plainly (bash cannot export arrays at all; layers are sourced in-shell, soexportwas inert on bash ≥ 4.4 and harmful on 3.2).Real consumer installs were never affected: install.sh:103 (
SKIPPED), install.sh:129 (SHIPPED_DOCS), setup.d/70-deps.sh:209/226 (DEVDEPS/RUNTIME_DEPS) initialize all four arrays non-empty at top level before the affected expansions run.Changes
setup.d/20-agents.sh— §3cSHIPPED_DOCSloop: bash-3.2-safe${arr[@]+"${arr[@]}"}guard (60-ci.sh:156 idiom).setup.d/99-finalize.sh— step-4 fallback printfs:"${DEVDEPS[*]-}"/"${RUNTIME_DEPS[*]-}"(default-empty; byte-identical output when populated).tests/install-sh/layer-units.test.sh—_setup_dispatcher_scope: array vars use plain assignment instead ofexport arr=()(bash-3.2 function-local export bug), with an explanatory comment.Failing → green evidence
Before (staging HEAD
11adac392, macOS/bin/bash3.2.57):Underlying errors (stderr un-suppressed):
After (this branch):
Regression battery run locally:
byte-identical.test.sh8/8 combinations unchanged + shellcheck clean (0.11.0);f8-agents-scripts-shipped.test.shPASS=8 FAIL=0;tool-decisions-seed-integration.test.shPASS=7 FAIL=0.Historical probe (same minimal-scope sourcing against
git show <sha>:setup.d/<layer>): failures reproduce identically at1c08b1627(S1),6143327e0,02239d0b9,e159bf5e5,11adac392→ red since S1, not a recent regression.Backward sweep of the defect class
All
"${arr[@]}"/"${arr[*]}"sites insetup.d/*.sh+install.shenumerated (grep -nE '\$\{[A-Za-z_0-9]+\[[@*]\]'), each verdicted:_aif_missing/_aif_stepsinit :108-109, populated in tandem :119, guarded :220), setup.d/99-finalize.sh:160 (_l2_configsinit :148, non-empty branch), setup.d/99-finalize.sh:351 (guarded :344), setup.d/70-deps.sh:209-358 (literals defined just above use), install.sh:160+541 (SHIPPED_DOCSnon-empty literal :129), setup.d/lib.sh:241/252/263 (missingearly-return :214), lib.sh:284 (guarded :283), lib.sh:362 (freshearly-return :354).candidatescan be empty if zero skill files ship — unreachable for current stacks); tests/install-sh/tool-decisions-seed-integration.test.sh:34-38 (sameexport arr=()in-function pattern; its exercised path doesn't hit the arrays — suite currently PASS=7 FAIL=0).Observations (not fixed here — single-concern scope)
setup.d/lib.sh:348—for rel in "${candidates[@]}"would crash on bash 3.2 ifignore_shipped_configsever runs with zero shipped skill.mdfiles (not reachable for any current stack/preset).tests/install-sh/tool-decisions-seed-integration.test.sh:34-38— sameexport SHIPPED_DOCS=()-in-function pattern as the harness bug fixed here; currently green because its path doesn't expand those arrays. Same one-line class of fix if invited.Prior-art consult
Prior-art:trailer not required; pre-push hook did not flag.Test plan
bash tests/install-sh/layer-units.test.sh— PASS=27 FAIL=0, exit 0 (bash 3.2.57 macOS + bash 5.3.9 linux)bash tests/install-sh/byte-identical.test.sh— 8/8 byte-identical, shellcheck cleanbash tests/install-sh/f8-agents-scripts-shipped.test.sh— PASS=8 FAIL=0bash tests/install-sh/tool-decisions-seed-integration.test.sh— PASS=7 FAIL=0shellcheck --exclude=SC2034,SC2016,SC2317 setup.d/*.sh install.sh— clean (CI gate mirror)gh pr merge --squash --body "$(gh pr view <N> --json body -q .body)")§1.7 Self-discipline check (REQUIRED if PR touches discipline-bearing files)
§1.7 Skipped: bug fix to installer shell layers + test harness (bash-3.2 empty-array safety); introduces or extends no discipline rule, principle, or shipped template.