Skip to content

Resolve the claude binary to an absolute path before spawning it#1460

Open
asdf8675309 wants to merge 1 commit into
danielmiessler:mainfrom
asdf8675309:fix/resolve-claude-bin
Open

Resolve the claude binary to an absolute path before spawning it#1460
asdf8675309 wants to merge 1 commit into
danielmiessler:mainfrom
asdf8675309:fix/resolve-claude-bin

Conversation

@asdf8675309

Copy link
Copy Markdown
Contributor

Resolve the claude binary to an absolute path before spawning it

Fixes silent, un-erroring failure of every scheduled (launchd/cron) caller of Inference.ts and the Algorithm CLI's claude-launching paths.

What

Inference.ts and algorithm.ts spawn the claude CLI by bare name (spawn('claude', ...), Bun.spawn(["claude", ...])). That relies on PATH to 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), a claude binary installed at ~/.local/bin/claude (or another non-standard location) is unreachable — spawn throws ENOENT.

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 than npm 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 on PATH only because an interactive shell's .zshrc/.zprofile puts it there. launchd/cron jobs and anything spawned via env -i/a minimal-env harness never source shell rc files, so they never see it, and spawn('claude', ...) ENOENTs. npm install -g users 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 — adds resolveClaudeBin(): checks CLAUDE_BIN env 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 so algorithm.ts can reuse it. The one spawn('claude', ...) call site now reads spawn(resolveClaudeBin(), ...).
  • LifeOS/install/LIFEOS/TOOLS/algorithm.ts — imports resolveClaudeBin from ./Inference and swaps all three claude-launching call sites: the parallel-worker Bun.spawn(["claude", ...]), and the two interactive-session spawn("claude", ...) calls (interactive and ideate modes).

No API change, no new dependency (existsSync was already available via fs).

Why it's safe

Pure resolution-order change with an identical last-resort fallback to today's behavior ("claude", letting PATH resolution proceed exactly as before). On any machine where claude is already on PATH — i.e. every normal interactive session today — resolveClaudeBin() either finds it in the known-locations list or via the $PATH scan 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:

$ env -i HOME=$HOME USER=$USER PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin" bun repro-old.ts
SPAWN_ERROR: ENOENT

$ env -i HOME=$HOME USER=$USER PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin" bun repro-new.ts
RESOLVED: /Users/<user>/.local/bin/claude
EXIT_OK: 0

$ bun repro-new.ts   # normal interactive PATH — no regression
RESOLVED: /Users/<user>/.local/bin/claude
EXIT_OK: 0
  • repro-old.ts mirrors the current spawn('claude', ...) call — ENOENT under the restricted PATH.
  • repro-new.ts mirrors the patched call with resolveClaudeBin() inlined — resolves the absolute path and the spawned process exits cleanly under the same restricted PATH.
  • Re-ran under a normal (unrestricted) PATH to confirm no regression to the everyday interactive case.
  • bun build Inference.ts bundles cleanly post-edit (syntax/import check).
  • Swept the full LifeOS/ source tree for every spawn/Bun.spawn/exec/execFile/execSync call naming "claude"/'claude' literally — these four were the only live-source hits; all four are now fixed. (One additional hit exists in Releases/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 against main.)

Tested on macOS 26.5.2 (Darwin 25.5.0), Bun 1.3.14.

Scope

LifeOS/install/LIFEOS/TOOLS/Inference.ts and LifeOS/install/LIFEOS/TOOLS/algorithm.ts only. No hook, skill, or settings changes.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant