Skip to content

fix(install): fail fast on Bash < 4.0 instead of crashing on mapfile - #25

Merged
twistedmelonman merged 1 commit into
mainfrom
claude/fix-bash4-requirement-guard
Aug 17, 2026
Merged

twistedmelonman merged 1 commit into
mainfrom
claude/fix-bash4-requirement-guard

Conversation

@twistedmelonman

Copy link
Copy Markdown
Member

The bug

install.sh calls the mapfile builtin at five sites (lines 251, 259, 502, 573, 585). mapfile was introduced in Bash 4.0.

The shebang is #!/usr/bin/env bash, which resolves to whatever bash is first on PATH. Stock macOS ships Bash 3.2.57 at /bin/bash — Apple has never shipped Bash 4+ because of the GPLv3 license change. So on a machine without Homebrew bash (or with it absent from PATH), this script runs under 3.2.

Reproduced on macOS:

$ /bin/bash -c 'mapfile -t x < /dev/null'
-bash: mapfile: command not found

Failure mode on stock macOS

Before this change there was no Bash version check anywhere in the script — no BASH_VERSION or BASH_VERSINFO reference at all. Combined with set -euo pipefail, that means:

  1. The script starts, parses args, does preflight checks, and performs real filesystem work.
  2. It reaches the first mapfile at line 251 and dies with mapfile: command not found.
  3. The user gets a cryptic builtin error with no indication that the problem is their Bash version — after changes have already begun.

The header comment compounded it, listing Requires: macOS, curl, python3 and omitting the Bash 4 requirement entirely.

The fix

An early fail-fast guard placed immediately after set -euo pipefail, before any side effects:

  • Checks ${BASH_VERSINFO[0]}, with :- so an unset BASH_VERSINFO is caught rather than tripping set -u.
  • Writes an actionable message to stderr: names the actual Bash version found, explains that macOS ships 3.2 and lacks mapfile, and gives the remedy (brew install bash plus the Apple-silicon and Intel invocation paths).
  • Exits 1.

The guard emits with plain echo ... >&2 rather than the script's error()/fatal() helpers, because those are defined further down the file and the guard has to run first. A comment in the code says so.

The Requires: header now states the Bash 4.0+ requirement so the docs match reality.

This is deliberately not a portability rewrite — the mapfile calls are untouched and no Bash 3.2 compatibility shim is added. The fix is a clear error plus honest documentation.

Verification

Under stock Bash 3.2:

$ /bin/bash ./install.sh --help
[error] archive-resolver requires Bash 4.0 or newer (found: 3.2.57(1)-release).
[error] macOS ships Bash 3.2 at /bin/bash, which lacks the 'mapfile' builtin this script uses.
[error] Remedy: install a newer Bash and run the script with it, e.g.
[error]   brew install bash
[error]   /opt/homebrew/bin/bash ./install.sh    # Apple silicon
[error]   /usr/local/bin/bash ./install.sh       # Intel
$ echo $?
1

Under Homebrew Bash 5.3.15 — normal behavior, usage printed, exit 0:

$ /opt/homebrew/bin/bash ./install.sh --help
Usage:
  ./install.sh --update-mirrors        Update mirrors.txt from Wikipedia (no root needed)
  sudo ./install.sh                    Install / update resolver files
  ...
$ echo $?
0

shellcheck -S info install.sh reports 8 SC2312 informational findings, all of which are pre-existing and unrelated to this change (verified by running shellcheck against the unmodified file — same 8). No new findings introduced, and no # shellcheck disable directives added.

install.sh uses the `mapfile` builtin (5 call sites), which requires Bash
4.0+. The shebang is `#!/usr/bin/env bash` and stock macOS ships Bash
3.2.57 at /bin/bash, so a user on stock macOS hits a cryptic
"mapfile: command not found" partway through the run — after the script
has already done real filesystem work under `set -euo pipefail`.

Add an early version guard immediately after `set -euo pipefail`, before
any side effects. It checks ${BASH_VERSINFO[0]} (guarding against the
variable being unset), writes an actionable message to stderr naming the
macOS 3.2 default and the `brew install bash` remedy, and exits 1.

Also correct the "Requires:" header, which omitted the Bash 4.0
requirement entirely.

Verified: `/bin/bash ./install.sh --help` now prints the guard message
and exits 1; `/opt/homebrew/bin/bash ./install.sh --help` prints usage
and exits 0. shellcheck -S info reports only pre-existing SC2312 infos.
@claude

claude Bot commented Aug 17, 2026

Copy link
Copy Markdown

The change adds a Bash version guard that exits cleanly with an actionable error message when the script is run under Bash < 4.0 (e.g. macOS stock /bin/bash 3.2). The guard fires before any filesystem writes, so it prevents the previous failure mode of a mid-execution "mapfile: command not found" after changes have already started. Condition logic and placement are correct.

VERDICT: PASS

@twistedmelonman
twistedmelonman merged commit 5b5823b into main Aug 17, 2026
4 of 8 checks passed
@twistedmelonman
twistedmelonman deleted the claude/fix-bash4-requirement-guard branch August 17, 2026 19:11
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