Skip to content

CI and docs: keep shell scripts bash 3.2 compatible - #286

Merged
jasperf merged 3 commits into
mainfrom
ci/bash32-syntax-check
Nov 9, 2026
Merged

jasperf merged 3 commits into
mainfrom
ci/bash32-syntax-check

Conversation

@jasperf

@jasperf jasperf commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a bash32-syntax job to .github/workflows/go-build.yml. It runs /bin/bash -n over every tracked .sh on a macos-latest runner, where /bin/bash is bash 3.2. That's the same bash wp-ops runs scripts with on macOS.

Why

TestShellScriptsParseWithSystemBash (added in 5.45.2) runs in the existing build job on ubuntu-latest. There /bin/bash is bash 5, which accepts syntax that 3.2 rejects. So it passes even with a bug like the 5.45.1 db-pull regression (case … ;; inside a heredoc inside $( … )), which failed for every macOS user. This job checks with the real 3.2 parser on every PR that touches the workflow's existing paths.

Why bash 3.2

  • macOS's /bin/bash is Apple's bash 3.2.57, the last GPLv2 release. Bash moved to GPLv3 with 4.0, and Apple doesn't ship GPLv3 software (anti-tivoization, DRM and patent terms), so bash 4+ will never be the macOS default. macOS's default login shell is zsh, but zsh never runs these scripts; each script runs under the bash its shebang names.
  • Homebrew doesn't install bash on its own. Bash 5.x only appears after an explicit brew install bash, and even then a #!/bin/bash script still gets 3.2. Of the 54 scripts, 29 use #!/bin/bash and 25 use #!/usr/bin/env bash, which gets 3.2 on any Mac without Homebrew bash.
  • So staying 3.2-compatible is the only baseline that works for every macOS user. Requiring bash 5 (depends_on formula: "bash" in the cask) would add a dependency just to allow newer syntax.

Documentation

  • AGENTS.md: a bash 3.2 compatibility rule under Coding Style, listing the constructs to avoid (declare -A, ${var,,}, mapfile, &>>, negative indexes, heredocs inside $( … )) and the read -r -d '' alternative. The validation steps now use /bin/bash -n instead of bash -n, because plain bash is Homebrew's 5.x on a dev Mac, which is how 5.45.1 slipped through. It also explains the two automated guards and what they don't catch.
  • CLAUDE.md: the same rule under Notes for AI Assistants.
  • README.md (Requirements) and scripts/README.md (Prerequisites): the built-in bash 3.2 is enough, and Homebrew bash isn't needed.

Notes

  • The check covers syntax only. Bash-4 features that parse on 3.2 but fail when they run (${var,,}, mapfile) still get through.
  • Failing scripts are reported as ::error file=… annotations, and the job prints how many scripts it checked.
  • No Go toolchain is needed, so it finishes in seconds. macOS runner minutes are free for public repos.
  • CHANGELOG: an entry under Unreleased, with no version bump since this changes CI only.

Testing

  • Ran the step locally under /bin/bash 3.2: 54 scripts checked, exit 0.
  • The workflow YAML parses, with jobs build and bash32-syntax.

Add a macos-latest job that runs /bin/bash -n over every tracked .sh.
The Go syntax test runs on Ubuntu, where /bin/bash is bash 5, so it
could not catch the 5.45.1 db-pull regression that only 3.2 rejects.
macOS /bin/bash is Apple's 3.2 and Homebrew bash is opt-in, so shipped
scripts must parse and run on 3.2. AGENTS.md lists the constructs to
avoid and switches the validation step from bash -n to /bin/bash -n,
since plain bash is Homebrew's 5.x on a dev Mac. CLAUDE.md carries the
same rule; the READMEs note the built-in bash is enough.
@jasperf jasperf changed the title CI: check shell scripts parse under macOS bash 3.2 CI and docs: keep shell scripts bash 3.2 compatible Nov 9, 2026
@jasperf
jasperf merged commit d7b9078 into main Nov 9, 2026
2 checks passed
@jasperf
jasperf deleted the ci/bash32-syntax-check branch November 9, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant