Resolve the claude binary to an absolute path before spawning it#1460
Open
asdf8675309 wants to merge 1 commit into
Open
Resolve the claude binary to an absolute path before spawning it#1460asdf8675309 wants to merge 1 commit into
asdf8675309 wants to merge 1 commit into
Conversation
Bare spawn('claude'/"claude") relies on PATH, which ENOENTs under a
restricted launchd/cron PATH (minimal PATH lacks ~/.local/bin). Adds
resolveClaudeBin() to Inference.ts (env override -> known install
paths -> PATH scan -> bare-string fallback, preserving prior
behavior) and wires it into all four claude-spawning call sites
across Inference.ts and algorithm.ts.
Tracked as asdf8675309/lifeos-work-tracker#4.
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.
Resolve the
claudebinary to an absolute path before spawning itFixes silent, un-erroring failure of every scheduled (launchd/cron) caller of
Inference.tsand the Algorithm CLI'sclaude-launching paths.What
Inference.tsandalgorithm.tsspawn theclaudeCLI by bare name (spawn('claude', ...),Bun.spawn(["claude", ...])). That relies onPATHto find the binary. It's fine in an interactive shell, but under a restricted launchd/cron environment (e.g.PATH=/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin), aclaudebinary installed at~/.local/bin/claude(or another non-standard location) is unreachable —spawnthrowsENOENT.The failure is silent by default: a caller that doesn't check
proc.on("error", ...)sees the child process simply never produce output, which downstream code can misread as "nothing to do" rather than "the binary never launched." This is the confirmed root cause of a scheduled inference job silently going dark for days on a real install before anyone noticed the gap.Who this hits: anyone who installed Claude Code via the native installer (
curl -fsSL https://claude.ai/install.sh | bash) rather thannpm install -g @anthropic-ai/claude-code. The native installer places the binary at~/.local/bin/claude(a symlink into~/.local/share/claude/versions/<version>) — XDG-convention territory that's onPATHonly because an interactive shell's.zshrc/.zprofileputs it there. launchd/cron jobs and anything spawned viaenv -i/a minimal-env harness never source shell rc files, so they never see it, andspawn('claude', ...)ENOENTs.npm install -gusers are typically unaffected because the npm global bin dir is more often already on the system-level PATH. This isn't a one-off local misconfiguration — it reproduces on any fresh machine set up with the native installer.Change
Two files, one new exported helper, four call sites:
LifeOS/install/LIFEOS/TOOLS/Inference.ts— addsresolveClaudeBin(): checksCLAUDE_BINenv override → a short list of known install locations (~/.local/bin/claude,~/.claude/local/claude,/opt/homebrew/bin/claude,/usr/local/bin/claude,/usr/bin/claude) → a manual scan of$PATH→ falls back to the bare string"claude"(preserves prior behavior if nothing else matches). Result is memoized. Exported soalgorithm.tscan reuse it. The onespawn('claude', ...)call site now readsspawn(resolveClaudeBin(), ...).LifeOS/install/LIFEOS/TOOLS/algorithm.ts— importsresolveClaudeBinfrom./Inferenceand swaps all threeclaude-launching call sites: the parallel-workerBun.spawn(["claude", ...]), and the two interactive-sessionspawn("claude", ...)calls (interactiveandideatemodes).No API change, no new dependency (
existsSyncwas already available viafs).Why it's safe
Pure resolution-order change with an identical last-resort fallback to today's behavior (
"claude", lettingPATHresolution proceed exactly as before). On any machine whereclaudeis already onPATH— i.e. every normal interactive session today —resolveClaudeBin()either finds it in the known-locations list or via the$PATHscan and behaves identically to the bare string. Nothing changes for the common case; the fix only activates when the bare name would otherwise have failed.Verification
Reproduced the bug and confirmed the fix under the exact restricted PATH a launchd/cron job runs with:
repro-old.tsmirrors the currentspawn('claude', ...)call —ENOENTunder the restricted PATH.repro-new.tsmirrors the patched call withresolveClaudeBin()inlined — resolves the absolute path and the spawned process exits cleanly under the same restricted PATH.bun build Inference.tsbundles cleanly post-edit (syntax/import check).LifeOS/source tree for everyspawn/Bun.spawn/exec/execFile/execSynccall naming"claude"/'claude'literally — these four were the only live-source hits; all four are now fixed. (One additional hit exists inReleases/v2.3/.claude/skills/CORE/Tools/Inference.ts, an archived historical release snapshot, not live source — left untouched, out of scope for a bug-fix PR againstmain.)Tested on macOS 26.5.2 (Darwin 25.5.0), Bun 1.3.14.
Scope
LifeOS/install/LIFEOS/TOOLS/Inference.tsandLifeOS/install/LIFEOS/TOOLS/algorithm.tsonly. No hook, skill, or settings changes.