From 7c08f22964483a44322b159735ac203afb18cef1 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Mon, 28 Sep 2026 23:45:23 +0530 Subject: [PATCH 1/3] docs: add migrated macOS recovery guidance --- docs/bootstrap.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/docs/bootstrap.md b/docs/bootstrap.md index 337fbafe..de8f61d0 100644 --- a/docs/bootstrap.md +++ b/docs/bootstrap.md @@ -66,6 +66,95 @@ exec "$SHELL" -l integration remains an explicit `basectl update-profile` step so the user can see what was installed before Base changes future interactive shells. +## Inherited Or Migrated macOS Accounts + +An account restored from Time Machine, migrated from another Mac, or shared +with a previous owner can retain a stale shell profile and Homebrew state. On +Apple Silicon, the most common failure is an Intel Homebrew under +`/usr/local` being selected by a Rosetta-translated shell even though native +Homebrew is installed under `/opt/homebrew`. Homebrew's Ruby traceback may +appear before Base is mentioned, but the underlying problem is usually the +process architecture, prefix selection, Xcode license, or prefix ownership. + +Run this read-only diagnostic as the target user before retrying an install: + +```bash +printf 'machine=%s\n' "$(uname -m)" +printf 'translated=%s\n' "$(sysctl -in sysctl.proc_translated 2>/dev/null || printf '0')" +printf 'shell=%s\n' "${SHELL:-unknown}" +printf 'path=%s\n' "$PATH" + +for candidate in /opt/homebrew/bin/brew /usr/local/bin/brew; do + if [ -x "$candidate" ]; then + printf 'brew=%s\n' "$candidate" + file "$candidate" + "$candidate" --prefix 2>&1 || true + fi +done + +command -v brew || true +brew --config 2>&1 || true +``` + +On Apple Silicon, a native terminal should report `machine=arm64` and +`translated=0`; prefer `/opt/homebrew/bin/brew`. If the process is +Rosetta-translated (`machine=x86_64` and `translated=1`), open a native +terminal and put `/opt/homebrew/bin` before `/usr/local/bin` for that shell +before continuing. On an Intel Mac, `/usr/local/bin/brew` is the expected +prefix. Do not select a prefix only because it appears first on `PATH`; check +the architecture and `brew --prefix` result together. + +Check the developer-tool boundary separately: + +```bash +/usr/bin/xcrun --find clang +/usr/bin/xcode-select --print-path +``` + +If either command reports that the Command Line Tools or Xcode license is +missing, complete Apple's interactive installation or license-acceptance flow +as the target user and rerun the read-only checks. Do not hide that prompt in a +non-interactive bootstrap pipeline. + +After selecting an architecture-compatible `brew`, inspect prefix ownership +and the lock directory before `brew install`: + +```bash +brew_prefix="$(brew --prefix)" +ls -ld "$brew_prefix" "$brew_prefix/var" "$brew_prefix/var/homebrew" \ + "$brew_prefix/var/homebrew/locks" 2>/dev/null || true +test -w "$brew_prefix" && printf 'prefix-writable=yes\n' || printf 'prefix-writable=no\n' +test -w "$brew_prefix/var/homebrew/locks" && \ + printf 'locks-writable=yes\n' || printf 'locks-writable=no\n' +``` + +The prefix and its lock directory must be owned and writable for the account +that is running Base. If another macOS user owns them, stop and ask the owner +or your device administrator to repair the Homebrew installation. Base must +not automatically run `sudo chown`, recursively change ownership, delete lock +directories, or reset Homebrew state. A source-checkout install is the safer +alternative when the shared Homebrew prefix cannot be repaired or is not +owned by the target account; use the [source checkout install recipe](#source-checkout-install-recipe) +instead. + +Once the checks agree, use the selected native Homebrew explicitly and finish +the normal Base setup: + +```bash +export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" # Apple Silicon native shell +brew trust basefoundry/base +brew install basefoundry/base/base +basectl setup --dry-run +basectl setup +basectl update-profile +exec "$SHELL" -l +``` + +For an Intel Mac, omit the `PATH` line or use `/usr/local/bin` first. If the +architecture, prefix, ownership, or Xcode checks do not agree, use the source +checkout path or stop with the collected read-only output; do not continue to +the Homebrew install merely to obtain a longer downstream traceback. + If `basectl` reports that the current Bash is too old, repair just that first: ```bash From 9dff50ba5bb166c3c788802ee9ea1d5ed2afa08d Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:50:06 +0530 Subject: [PATCH 2/3] docs: make macOS recovery guidance canonical --- docs/bootstrap.md | 79 ++++++++++++++++++++---------------- tests/test_bootstrap_docs.py | 12 ++++++ 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/docs/bootstrap.md b/docs/bootstrap.md index de8f61d0..e86a5a62 100644 --- a/docs/bootstrap.md +++ b/docs/bootstrap.md @@ -76,11 +76,15 @@ Homebrew is installed under `/opt/homebrew`. Homebrew's Ruby traceback may appear before Base is mentioned, but the underlying problem is usually the process architecture, prefix selection, Xcode license, or prefix ownership. -Run this read-only diagnostic as the target user before retrying an install: +Run this read-only diagnostic as the target user before retrying an install. It +inspects each known Homebrew path explicitly before selecting the compatible +prefix for the remainder of the current shell: ```bash -printf 'machine=%s\n' "$(uname -m)" -printf 'translated=%s\n' "$(sysctl -in sysctl.proc_translated 2>/dev/null || printf '0')" +machine="$(uname -m)" +translated="$(sysctl -in sysctl.proc_translated 2>/dev/null || printf '0')" +printf 'machine=%s\n' "$machine" +printf 'translated=%s\n' "$translated" printf 'shell=%s\n' "${SHELL:-unknown}" printf 'path=%s\n' "$PATH" @@ -92,17 +96,27 @@ for candidate in /opt/homebrew/bin/brew /usr/local/bin/brew; do fi done -command -v brew || true -brew --config 2>&1 || true +if [ "$machine" = "arm64" ] && [ "$translated" = "0" ]; then + export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" +elif [ "$machine" = "x86_64" ] && [ "$translated" = "0" ]; then + export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH" +fi + +if command -v brew >/dev/null 2>&1; then + printf 'selected-brew=%s\n' "$(command -v brew)" + brew --config 2>&1 || true +else + printf 'selected-brew=unresolved; stop before ownership checks\n' +fi ``` On Apple Silicon, a native terminal should report `machine=arm64` and -`translated=0`; prefer `/opt/homebrew/bin/brew`. If the process is -Rosetta-translated (`machine=x86_64` and `translated=1`), open a native -terminal and put `/opt/homebrew/bin` before `/usr/local/bin` for that shell -before continuing. On an Intel Mac, `/usr/local/bin/brew` is the expected -prefix. Do not select a prefix only because it appears first on `PATH`; check -the architecture and `brew --prefix` result together. +`translated=0`; the diagnostic puts `/opt/homebrew/bin` first for that shell. +If the process is Rosetta-translated (`machine=x86_64` and `translated=1`), +open a native terminal before continuing. On an Intel Mac, +`/usr/local/bin/brew` is the expected prefix. Do not select a prefix only +because it appears first on `PATH`; check the architecture and the explicit +candidate `--prefix` results together. Check the developer-tool boundary separately: @@ -120,12 +134,20 @@ After selecting an architecture-compatible `brew`, inspect prefix ownership and the lock directory before `brew install`: ```bash -brew_prefix="$(brew --prefix)" -ls -ld "$brew_prefix" "$brew_prefix/var" "$brew_prefix/var/homebrew" \ - "$brew_prefix/var/homebrew/locks" 2>/dev/null || true -test -w "$brew_prefix" && printf 'prefix-writable=yes\n' || printf 'prefix-writable=no\n' -test -w "$brew_prefix/var/homebrew/locks" && \ - printf 'locks-writable=yes\n' || printf 'locks-writable=no\n' +if command -v brew >/dev/null 2>&1; then + brew_prefix="$(brew --prefix 2>/dev/null || true)" + if [ -n "$brew_prefix" ]; then + ls -ld "$brew_prefix" "$brew_prefix/var" "$brew_prefix/var/homebrew" \ + "$brew_prefix/var/homebrew/locks" 2>/dev/null || true + test -w "$brew_prefix" && printf 'prefix-writable=yes\n' || printf 'prefix-writable=no\n' + test -w "$brew_prefix/var/homebrew/locks" && \ + printf 'locks-writable=yes\n' || printf 'locks-writable=no\n' + else + printf 'brew-prefix=unresolved; stop before ownership checks\n' + fi +else + printf 'brew=unresolved; stop before ownership checks\n' +fi ``` The prefix and its lock directory must be owned and writable for the account @@ -137,23 +159,12 @@ alternative when the shared Homebrew prefix cannot be repaired or is not owned by the target account; use the [source checkout install recipe](#source-checkout-install-recipe) instead. -Once the checks agree, use the selected native Homebrew explicitly and finish -the normal Base setup: - -```bash -export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" # Apple Silicon native shell -brew trust basefoundry/base -brew install basefoundry/base/base -basectl setup --dry-run -basectl setup -basectl update-profile -exec "$SHELL" -l -``` - -For an Intel Mac, omit the `PATH` line or use `/usr/local/bin` first. If the -architecture, prefix, ownership, or Xcode checks do not agree, use the source -checkout path or stop with the collected read-only output; do not continue to -the Homebrew install merely to obtain a longer downstream traceback. +Once the checks agree, follow the [canonical Homebrew install recipe](#homebrew-install-recipe). +The diagnostic has already placed the architecture-compatible prefix first on +`PATH` for the current native shell. If the architecture, prefix, ownership, +or Xcode checks do not agree, use the source checkout path or stop with the +collected read-only output; do not continue to the Homebrew install merely to +obtain a longer downstream traceback. If `basectl` reports that the current Bash is too old, repair just that first: diff --git a/tests/test_bootstrap_docs.py b/tests/test_bootstrap_docs.py index 7bb12231..b27de72a 100644 --- a/tests/test_bootstrap_docs.py +++ b/tests/test_bootstrap_docs.py @@ -50,6 +50,18 @@ def test_bootstrap_docs_explain_mutable_homebrew_default_rationale() -> None: assert "BASE_HOMEBREW_INSTALLER_SHA256" in normalized +def test_migrated_macos_recovery_uses_canonical_recipe_after_safe_selection() -> None: + text = BOOTSTRAP_DOC.read_text(encoding="utf-8") + recovery = section(text, "## Inherited Or Migrated macOS Accounts", "## Install Mode") + + assert "[canonical Homebrew install recipe](#homebrew-install-recipe)" in recovery + assert "brew trust basefoundry/base" not in recovery + assert "brew install basefoundry/base/base" not in recovery + assert "basectl setup --dry-run" not in recovery + assert recovery.index('export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"') < recovery.index('brew_prefix="$(brew --prefix') + assert recovery.index('export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"') < recovery.index("brew --config") + + def test_readme_trust_conscious_proof_reviews_manifest_trust_before_demo() -> None: text = README.read_text(encoding="utf-8") proof = section(text, "### Trust-Conscious Proof, No Dotfile Changes", "## How Base Fits") From 57a89b0e9a3bbfc2c81974ca780b9279258cdb29 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Tue, 29 Sep 2026 23:41:10 +0530 Subject: [PATCH 3/3] test(docs): satisfy pylint line limit --- tests/test_bootstrap_docs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_bootstrap_docs.py b/tests/test_bootstrap_docs.py index b27de72a..95bc31a8 100644 --- a/tests/test_bootstrap_docs.py +++ b/tests/test_bootstrap_docs.py @@ -58,8 +58,9 @@ def test_migrated_macos_recovery_uses_canonical_recipe_after_safe_selection() -> assert "brew trust basefoundry/base" not in recovery assert "brew install basefoundry/base/base" not in recovery assert "basectl setup --dry-run" not in recovery - assert recovery.index('export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"') < recovery.index('brew_prefix="$(brew --prefix') - assert recovery.index('export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"') < recovery.index("brew --config") + path_export = 'export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"' + assert recovery.index(path_export) < recovery.index('brew_prefix="$(brew --prefix') + assert recovery.index(path_export) < recovery.index("brew --config") def test_readme_trust_conscious_proof_reviews_manifest_trust_before_demo() -> None: