You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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@22 → brew 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
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.
What to build
macos/bootstrap.shalready installs Node viafnm install --lts, which is the right design. But two real Macs (mbp13and 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-hocbrew install nodethat predated this script, and both crashed Claude Code andqmd(viabetter-sqlite3ABI 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
brewinstallation of thenodeformula (any Current-line version) and eitherbrew unlink nodes it OR warns and prompts before continuing — pre-existing brew Node must not shadowfnmnode@22andbrew link --force --overwrite node@22directly — single-flag toggle, documented in README/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) socompinitdoesn't prompt andbrew installdoesn't fail withPermission denied @ rb_sysopen— both happened on mbp13 on 2026-05-13zshrc.snippet.shappends a guard that warns on shell startup ifnode -vis on an odd-line release (v23/v25/v27/v29), and prints the recovery command inline--restore-globals <file>flag that reads a list of npm package names (one per line, as produced bynpm 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 silentlybrew install node@22→brew unlink node && brew link --force --overwrite node@22→ reinstall globals → uninstall old kegmbp13and the primary Mac to confirm both reach a green state; close existingnpmnot 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 issueBlocked by
None — can start immediately. Touches existing issue #3 (
npmnot 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.