Skip to content

macos/bootstrap.sh: harden against pre-existing brew Node + apply lessons from 2026-05-13 incidents #5

@AojdevStudio

Description

@AojdevStudio

What to build

macos/bootstrap.sh already installs Node via fnm install --lts, which is the right design. But two real Macs (mbp13 and the primary dev Mac) were both running odd-line Node (v25.7.0 and v26.0.0 respectively) on 2026-05-13, both via ad-hoc brew install node that predated this script, and both crashed Claude Code and qmd (via better-sqlite3 ABI mismatch). The fix took ~45 min across the two boxes.

Harden the macOS bootstrap so it's safe to run on a Mac that's already been touched ad-hoc, and so it produces a system where Node version cannot silently drift to a non-LTS Current-line release on the next brew upgrade. This is a defensive/idempotency pass, not a redesign.

Acceptance criteria

  • Script detects an existing brew installation of the node formula (any Current-line version) and either brew unlink nodes it OR warns and prompts before continuing — pre-existing brew Node must not shadow fnm
  • Script offers a Plan B: when the user wants a stable, brew-only setup (no fnm shell hook needed), install node@22 and brew link --force --overwrite node@22 directly — single-flag toggle, documented in README
  • Script pre-flights and fixes ownership of /usr/local/share/zsh, /usr/local/share/zsh/site-functions, /usr/local/var/homebrew, /usr/local/Cellar, /opt/homebrew/share/zsh, /opt/homebrew/var/homebrew (Intel + Apple Silicon paths) so compinit doesn't prompt and brew install doesn't fail with Permission denied @ rb_sysopen — both happened on mbp13 on 2026-05-13
  • zshrc.snippet.sh appends a guard that warns on shell startup if node -v is on an odd-line release (v23/v25/v27/v29), and prints the recovery command inline
  • Script supports a --restore-globals <file> flag that reads a list of npm package names (one per line, as produced by npm ls -g --depth=0 --json | jq -r '.dependencies | keys[]') and reinstalls them on the now-pinned Node, so machine-to-machine moves and Node-version flips don't lose globals silently
  • README has a "Migrating a Mac that was already set up ad-hoc" section showing the exact commands run on 2026-05-13: save globals list → brew install node@22brew unlink node && brew link --force --overwrite node@22 → reinstall globals → uninstall old keg
  • Script is idempotent: rerunning on an already-bootstrapped Mac produces zero changes and exit 0
  • Run end-to-end on mbp13 and the primary Mac to confirm both reach a green state; close existing npm not found during Claude Code / Codex install — fnm not activated in piped execution scope #3 if the fnm-in-pipe fix is covered by the same patch, otherwise reference it from this issue

Blocked by

None — can start immediately. Touches existing issue #3 (npm not found during Claude Code / Codex install — fnm not activated in piped execution scope); fold its fix in if the activation order is part of this hardening pass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions