Repository navigation
fix(setup): layered binary resolution + manual override — kill the A1 first-launch lockout (#495) - #504
Merged
Conversation
…fish/nushell runLoginShell used to exec $SHELL with a hardcoded ['-lc', script]. With fish/nushell/xonsh/elvish/pwsh as the login shell that probe fails (flag grammar / command -v semantics differ), resolveToolPath mapped the failure to null, checkPrerequisites marked required providers found:false, and SetupGate rendered with no Continue button — a total first-launch lockout on a correctly-provisioned machine (#495 A1). Now the probe shell is picked per call: $SHELL when its basename is a known POSIX shell, else the first of /bin/zsh → /bin/bash → /bin/sh that exists. Non-POSIX users' shell-private PATH additions are covered by the direct PATH scan added to binaryResolver in the next commit, not by teaching this file per-shell dialects. Refs #495 (A1, 1/3) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hell probe fails resolveToolPath treated any login-shell probe failure as "tool missing" (bare catch → null). That made the probe a single point of failure for the whole product: checkPrerequisites gates required providers on it, and a false negative locks SetupGate (#495 A1). The probe stays layer 1 — its answer reflects the user's curated login environment, so a version manager's shim must keep beating a stale system copy. But both the empty-result and thrown cases now fall through to a direct X_OK scan of process.env.PATH plus the standard install dirs (~/.local/bin, volta/asdf/bun/cargo shims, brew prefixes, /usr/bin, /bin). The scan cannot be poisoned by rc files and covers Finder launches where launchd's minimal PATH is all we inherit. Refs #495 (A1, 2/3) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ver lock the app A required tool falsely reported missing used to be a dead end: SetupGate renders no Continue button when ready:false, Retry re-runs the identical probe, and providers are installable:false — no affordance existed to tell the app where the binary actually is (#495 A1). New setup:set-tool-path IPC: validates the pasted path is an absolute executable, persists it through the same updateToolPaths() the automatic probe uses (so revalidateToolchain and PATH augmentation treat it like any probed path), and returns a fresh check so the gate unlocks in one round-trip. SetupGate grows an 'Enter path manually…' disclosure on missing required rows. Load-bearing companion fix in checkPrerequisites: its write-back deleted persisted paths whenever a probe returned null, which would have erased the manual override seconds after it was saved — and was already silently wiping previously-good cached paths on any transient probe failure. The probe now falls back to the persisted path when that file still execs; a genuinely-removed tool still clears because the fallback re-checks the file, not the bookkeeping. Refs #495 (A1, 3/3) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…utables, make manual overrides win over auto-resolution
Finding 1 (BLOCKER): isExecutable() was a bare access(path, X_OK), and
POSIX X_OK on a directory means "searchable" — so /usr/local/bin passed
manual-override validation and the provider later tried to spawn a
directory. binaryResolver now exposes classifyExecutable(): stat
(following symlinks, so brew/volta symlink layouts still pass) must say
isFile() AND the file must have X_OK. isExecutable() delegates to it, so
every resolver layer (probe validation, PATH/well-known-dir scan,
persisted-path fallback) inherits the file check; setup:set-tool-path
uses the verdict directly and returns a distinct message for the
directory case (the natural typo: pasting the containing dir).
Finding 2 (MINOR): a valid manual override was consulted only as a
FALLBACK after the probes, so any auto layer that found a different
binary silently replaced the user's ~/bin/claude-wrapper with PATH's
/usr/local/bin/claude and the write-back erased the override. Overrides
now live in their own persisted map (manualToolPaths — durable user
intent, defaulted to {} for pre-existing setup.json files) and a
still-valid override wins over every automatic layer in BOTH
auto-resolution entry points: checkPrerequisites (precedence 0, WHY
comment documents the full 0-3 order) and revalidateToolchain (which
runs every app launch and would otherwise clobber the override one
session later). Auto layers only take over when the override's file is
missing or no longer an executable regular file; intent stays recorded
and resumes winning if the file comes back.
Refs #504, #495 (A1).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Part 1 of the #495 Tier-A portability plan (
docs/superpowers/plans/2026-07-09-portability-tier-a-any-mac-hardening.mdon theplan/portability-495-496branch): finding A1, the only CRITICAL — a user withfish/nushellas$SHELL(or a PATH added by a non-login rc block) got every required tool falsely reported missing, and SetupGate rendered with no Continue button and no way through. Total first-launch lockout on a correctly-provisioned Mac.Three commits, three layers — a false negative now has to get through all of them:
shell.ts):runLoginShelluses$SHELLonly when its basename is a known POSIX shell (bash/zsh/sh/dash/ksh); otherwise it probes through the first of/bin/zsh → /bin/bash → /bin/shthat exists, picked per call. No per-shell dialect handling on purpose — fish-only PATH additions are layer 2's job.binaryResolver.ts): both the empty-result and thrown-probe cases now fall through to anX_OKscan ofprocess.env.PATHplus standard install dirs (~/.local/bin, volta/asdf/bun/cargo shims, brew prefixes,/usr/bin,/bin). The login-shell probe stays first so a version manager's shim keeps beating a stale system copy.setup:set-tool-pathIPC + SetupGate UI): missing required rows get an "Enter path manually…" input. Validates absolute + executable, persists through the sameupdateToolPaths()the probe uses, returns a fresh check so the gate unlocks in one round-trip.Load-bearing companion fix
checkPrerequisites's write-back deleted persisted tool paths whenever a probe returned null — it would have erased the manual override seconds after saving it, and was already silently wiping good cached paths on any transient probe failure. The probe now falls back to the persisted path when that file still execs; genuinely-removed tools still clear because the fallback re-checks the file, not the bookkeeping.Verification
npx tsc -b tsconfig.node.json✅ andnpx tsc --noEmit -p tsconfig.web.json✅runLoginShellsucceeds withSHELL=/usr/local/bin/fish;resolveToolPath('git')returns an absolute path withSHELL=/nonexistent/bin/bash(probe ENOENT → scan fallback) ✅Refs #495 (A1). Parts 2–4 of the plan follow as separate PRs.
🤖 Generated with Claude Code