--- title: Install / Build Script Portability type: concept updated: 2026-04-28 --- # Install / Build Script Portability `scripts/install.sh` and `scripts/build.sh` must run on three combos: 1. Windows + git-bash (primary dev workstation) 2. Linux + bash (typical server) 3. macOS + bash (the gotcha) Stock macOS ships **bash 3.2** (frozen at 2007 because GPLv3) and **BSD `find`**. Both diverge from what Linux's bash 4+ / GNU `find` accept. ## What broke before the fix Both scripts originally used: ```bash mapfile -t names < <(find "$SRC" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort) ``` Two failures on stock Mac: - **`mapfile`** — bash 4+ builtin. Bash 3.2 throws `mapfile: command not found`. - **`find -printf`** — GNU extension. BSD find throws `find: -printf: unknown primary or operator` and exits non-zero, killing the pipeline under `set -euo pipefail`. Result: `bash scripts/install.sh` died on line 11 with a confusing error, before copying anything. Same for `build.sh`. ## Fix Replace with shell glob — pure POSIX-compatible bash 3.2 syntax, no `find` flavor dependency: ```bash names=() for d in "$SRC"/*/; do [ -d "$d" ] || continue names+=("$(basename "$d")") done IFS=$'\n' names=($(printf '%s\n' "${names[@]}" | sort)) unset IFS ``` - `for d in dir/*/` — glob expansion. Trailing slash filters to directories. - `[ -d "$d" ] || continue` — guards the empty-dir case (when no subdirs exist, the literal pattern `dir/*/` is left unexpanded by default; the test rejects it). - `basename` — POSIX, present everywhere. - `IFS=$'\n' ... sort` — preserves alphabetical order to match the previous behavior (`find | sort`). `$'...'` ANSI-C quoting is bash 2+. The whole approach works identically on bash 3.2 (Mac), bash 4 (Linux), bash 5 (modern Linux + brew on Mac), and the bash bundled with git-for-Windows. ## What still requires bash, not POSIX `sh` The shebangs are still `#!/usr/bin/env bash`. Arrays (`names=()`, `names+=(...)`, `"${names[@]}"`) are bash, not POSIX. That's fine — bash is on every target OS. Demoting to `sh` would force a much uglier rewrite for no real portability gain. ## What was already portable - `cp -R "$src" "$dst"` — POSIX flag, works everywhere. - `rm -rf` — POSIX. - `mkdir -p` — POSIX. - `~/.claude/skills/` — `~` expands the same on all three shells. - The `zip`/PowerShell branching in `build.sh` already handled "no zip on stock Windows" — the Mac path simply uses `zip`, which ships in `/usr/bin/zip` on every macOS release since forever. ## Test coverage Manually verified on Windows + git-bash after the fix: 16 skills discovered, sorted, installed to `$CLAUDE_SKILLS_DIR`. `build.sh active-platform` produced a valid archive. No real BSD-find / bash-3.2 environment was tested — confidence comes from the fact that the new code uses only POSIX-shell + bash-2 features, all explicitly supported on stock macOS. The `archive-roundtrip-test` task on the board would catch any regression here automatically.