diff --git a/.context/LEARNINGS.md b/.context/LEARNINGS.md index 753580cf8..5f13ad420 100644 --- a/.context/LEARNINGS.md +++ b/.context/LEARNINGS.md @@ -6,6 +6,7 @@ | 2026-08-23 | Codex trust and hook wiring facts verified against codex 0.148 | | 2026-08-23 | hack scripts must survive macOS /bin/bash 3.2 and BSD grep | | 2026-08-23 | make lint SA5011 false positives mean a corrupted golangci-lint cache | +| 2026-08-19 | Empty-array expansion under set -u kills lint-drift.sh on stock macOS bash 3.2 | | 2026-07-25 | Using the proprietary sibling repo as design evidence leaks its internals into tracked files | | 2026-07-25 | Skill and doc examples of a serialized structure must round-trip through the real parser | | 2026-07-25 | A guard derived from a capability accessor silently lifts when the accessor is extended | @@ -88,6 +89,16 @@ DO NOT UPDATE FOR: --- +## [2026-08-19-211547] Empty-array expansion under set -u kills lint-drift.sh on stock macOS bash 3.2 + +**Context**: make audit had never passed on this stock macOS machine: lint-drift.sh died with "exclude_args[@]: unbound variable" because bash 3.2 treats expanding an empty array as an unset-variable error under set -u. (The sibling lint-docstrings.sh gotchas — apostrophe in a $( ) comment, grep -P on BSD grep — are covered by the 2026-08-23 "hack scripts must survive macOS /bin/bash 3.2 and BSD grep" learning.) + +**Lesson**: Bash 3.2 under set -u aborts on ${arr[@]} when the array is empty; bash 4.4+ made this legal, so Linux CI never sees it. + +**Application**: In hack/ scripts guard every possibly-empty array expansion with ${arr[@]+"${arr[@]}"}. Per specs/hack-script-portability.md. + +--- + ## [2026-07-25-124457] Using the proprietary sibling repo as design evidence leaks its internals into tracked files **Context**: While deciding the pd-m4 add-path shape, I read the sibling repo's convention file to settle the question, then quoted its guide text and attributed the decision to it in a tracked plan file. An unrelated build warning prompted the sweep that caught it. diff --git a/hack/lint-drift.sh b/hack/lint-drift.sh index d01b85b5e..99138b692 100755 --- a/hack/lint-drift.sh +++ b/hack/lint-drift.sh @@ -36,7 +36,10 @@ drift_grep() { for ex in "$@"; do exclude_args+=(--exclude="$ex") done - grep -rn --include='*.go' --exclude='*_test.go' "${exclude_args[@]}" \ + # ${arr[@]+...} guard: bash 3.2 (macOS default) treats an empty + # array expansion as unbound under `set -u`. + grep -rn --include='*.go' --exclude='*_test.go' \ + ${exclude_args[@]+"${exclude_args[@]}"} \ -E "$pattern" internal/ 2>/dev/null || true } diff --git a/specs/hack-script-portability.md b/specs/hack-script-portability.md new file mode 100644 index 000000000..bc4cdd8cc --- /dev/null +++ b/specs/hack-script-portability.md @@ -0,0 +1,39 @@ +# Spec: hack/ Script Portability — macOS Default Toolchain + +## Problem + +`make audit` fails before running a single real check on a stock +macOS machine (bash 3.2, BSD grep): + +`hack/lint-drift.sh` — `"${exclude_args[@]}"` on an empty array +aborts under `set -u` on bash 3.2 ("unbound variable"; bash 4.4+ +treats it as empty). + +This failure predates any feature work and masks real findings: the +audit gate cannot run at all on contributor machines with the default +macOS toolchain. + +The sibling `hack/lint-docstrings.sh` portability bugs (apostrophe in +a `$( … )` comment, `grep -cP` on BSD grep) were fixed upstream; see +`specs/lint-docstrings-macos-portability.md`. + +## Fix + +Minimal, behavior-preserving substitution that runs identically under +GNU and BSD toolchains: + +- `${arr[@]+"${arr[@]}"}` guard for empty-array expansion. + +## Non-Goals + +- Rewriting the lint scripts in Go (tracked in TASKS.md: "Replace + hack/lint-drift.sh with AST-based Go tests"; "Rewrite lint-style + scripts in Go as ctxctl subcommands"). This spec only unblocks the + gate until that lands. +- A full portability audit of every script under `hack/` (only the + `make audit` chain is in scope). + +## Verification + +`./hack/lint-drift.sh` completes on macOS (bash 3.2.57, BSD grep) +with the same findings as on a GNU toolchain: `lint-drift: clean`.