Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .context/LEARNINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ DO NOT UPDATE FOR:
<!-- INDEX:START -->
| Date | Learning |
|----|--------|
| 2026-06-10 | Stock macOS bash 3.2 treats empty-array expansion as unbound under set -u |
| 2026-06-07 | ctx-dream design principles (consolidated) |
| 2026-06-07 | internal/audit & compliance gates for new code (consolidated) |
| 2026-06-07 | Error handling: sentinels, unwrapping, and silent discards (consolidated) |
Expand Down Expand Up @@ -102,6 +103,16 @@ DO NOT UPDATE FOR:

---

## [2026-06-10-223128] Stock macOS bash 3.2 treats empty-array expansion as unbound under set -u

**Context**: make audit aborted at hack/lint-drift.sh line 39 on a stock Mac (bash 3.2.57) with 'exclude_args[@]: unbound variable' while gating the #93 TOCTOU fix; the script works fine on Linux bash 4+

**Lesson**: bash 3.2 (what every stock macOS ships, GPLv2 freeze) treats "${arr[@]}" on an empty array as an unbound-variable error under set -u; bash 4.4+ does not. Any 'set -u' script that expands a possibly-empty array breaks for every Mac contributor

**Application**: Guard with ${arr[@]+"${arr[@]}"} (parameter-expansion alternate form) wherever a possibly-empty array is expanded in hack/ scripts; test hack/ scripts with /bin/bash, not just homebrew bash

---

## [2026-06-07-170001] ctx-dream design principles (consolidated)

**Consolidated from**: 6 entries (2026-06-06 to 2026-06-07)
Expand Down
6 changes: 5 additions & 1 deletion hack/lint-drift.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ drift_grep() {
for ex in "$@"; do
exclude_args+=(--exclude="$ex")
done
grep -rn --include='*.go' --exclude='*_test.go' "${exclude_args[@]}" \
# ${arr[@]+...} guards the empty-array expansion: bash 3.2
# (stock macOS) treats "${arr[@]}" on an empty array as unbound
# under `set -u` and aborts the script.
grep -rn --include='*.go' --exclude='*_test.go' \
${exclude_args[@]+"${exclude_args[@]}"} \
-E "$pattern" internal/ 2>/dev/null || true
}

Expand Down
68 changes: 68 additions & 0 deletions specs/fix-lint-drift-bash32-empty-array.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Fix lint-drift Empty-Array Expansion on Bash 3.2

`hack/lint-drift.sh` aborted on stock macOS before running a
single check, which silently broke `make audit` (and therefore
the contributing guide's mandatory pre-PR gate) for every Mac
contributor.

## Problem

The `drift_grep` helper builds its `--exclude` flags in an
array and expands it unconditionally:

```bash
local exclude_args=()
for ex in "$@"; do
exclude_args+=(--exclude="$ex")
done
grep -rn --include='*.go' --exclude='*_test.go' "${exclude_args[@]}" \
-E "$pattern" internal/ 2>/dev/null || true
```

The script runs under `set -euo pipefail`. On bash 4.4+ an
empty `"${arr[@]}"` expands to zero words; on bash 3.2 — the
newest bash Apple ships, frozen at the GPLv2 boundary — the
same expansion is an **unbound variable** error under `set -u`:

```
./hack/lint-drift.sh: line 39: exclude_args[@]: unbound variable
```

Several `drift_grep` call sites pass no exclude globs (checks
2, 3, and 8), so the script dies on its first such call and
`make lint-style` → `make audit` fail before any drift check
executes.

## Solution

Guard the expansion with the parameter-expansion alternate
form, the canonical bash-3.2-safe idiom:

```bash
${exclude_args[@]+"${exclude_args[@]}"}
```

When the array is empty the outer expansion produces nothing;
when populated it reproduces the original quoted expansion
verbatim. Behavior on bash 4+ is unchanged. A comment at the
call site documents why the guard exists so a future cleanup
doesn't "simplify" it back.

Verified: `make audit` passes end-to-end on macOS
bash 3.2.57 with the guard in place.

## Out of Scope

- Hardening the other `hack/` scripts' array expansions. A
`grep -rn '\[@\]' hack/` sweep plus empirical bash 3.2
checks show the remaining sites are all safe today:
`lint-shellcheck.sh` exits on a `${#TARGETS[@]}` count
guard (count expansion does not trip `set -u` on 3.2)
before its element expansion; `build-all.sh` and
`detect-ai-typography.sh` expand arrays populated from
hardcoded non-empty literals. Those are latent-only
hazards (someone emptying a config array), not failures,
and belong to a separate sweep if ever.
- Requiring bash 4+ (e.g. a version check or `#!/usr/bin/env
bash4`). Contributors should not need a homebrew bash to run
the project's own audit gate.
Loading