Skip to content

fix(providers): replace Claude SDK embed with explicit binary-path resolver#1217

Merged
Wirasm merged 6 commits intodevfrom
fix/claude-binary-resolver-drop-embed
Apr 14, 2026
Merged

fix(providers): replace Claude SDK embed with explicit binary-path resolver#1217
Wirasm merged 6 commits intodevfrom
fix/claude-binary-resolver-drop-embed

Conversation

@Wirasm
Copy link
Copy Markdown
Collaborator

@Wirasm Wirasm commented Apr 14, 2026

Summary

UX Journey

Before

User                       Compiled Archon Binary             Claude Code SDK
────                       ──────────────────────             ───────────────
archon workflow run …  ──▶ load /embed import
                           cliPath = /Users/runner/.../cli.js  (frozen at CI build)
                           pass to SDK              ────────▶  spawn(bun, [cliPath, ...])
                                                               ❌ ENOENT — file does not exist on user's machine
                           "Claude Code crash:                    "Module not found
                            Module not found"      ◀──────────    /Users/runner/..."
sees cryptic error  ◀───── retry × 3, fail

After

User                       Compiled Archon Binary             Claude Code SDK
────                       ──────────────────────             ───────────────
archon workflow run …  ──▶ resolve binary path
                           ├─ CLAUDE_BIN_PATH?
                           ├─ config.assistants.claude.claudeBinaryPath?
                           └─ throw with install instructions ──┐
                                                                │
                           [if path present]                    │
                           pass to SDK              ────────▶  spawn(claude executable)
                                                               ✓ subprocess starts
                           stream events           ◀────────── streams response
sees response ◀────────── render to platform                    OR
                                                                │
                           [if path missing]                    ▼
sees actionable error ◀── *Claude Code not found.
                           macOS/Linux: curl -fsSL https://claude.ai/install.sh | bash
                                        export CLAUDE_BIN_PATH=\"\$HOME/.local/bin/claude\"
                           Windows:    irm https://claude.ai/install.ps1 | iex
                                        \$env:CLAUDE_BIN_PATH = \"...\"
                           Or via npm: ...
                           Or in config: ...*

Architecture Diagram

Before

                      ┌─────────────────────────────┐
                      │  packages/providers/claude  │
                      │                             │
                      │  provider.ts                │
                      │   ├─ import cliPath from    │
                      │   │  '@anthropic-ai/        │
                      │   │   claude-agent-sdk/     │
                      │   │   embed'                │ ── compile-time asset embed
                      │   │                         │    via Bun \`with { type: 'file' }\`
                      │   └─ pathToClaudeCodeExec:  │
                      │      cliPath                │
                      │                             │
                      │   executableArgs:           │
                      │     ['--no-env-file']       │ ── always passed
                      └─────────────────────────────┘

After

                      ┌─────────────────────────────────┐
                      │  packages/providers/claude      │
                      │                                 │
                      │  binary-resolver.ts  [+]        │ ── new
                      │   resolveClaudeBinaryPath(cfg)  │
                      │   ├─ env CLAUDE_BIN_PATH        │
                      │   ├─ config.claudeBinaryPath    │
                      │   └─ throw install instructions │
                      │                                 │
                      │  provider.ts  [~]               │
                      │   ├─ await resolveClaudeBinaryPath
                      │   ├─ pathToClaudeCodeExec:      │
                      │   │  resolvedCliPath  (cond)    │
                      │   └─ executableArgs:            │
                      │      cliPath?.endsWith('.js')   │ ── only for Bun-spawned cli.js
                      │       ? ['--no-env-file'] : [] │    (native binary rejects unknown flags)
                      │                                 │
                      │  config.ts [~]  parse           │
                      │     claudeBinaryPath            │
                      │                                 │
                      │  types.ts [~]  ClaudeProvider   │
                      │     Defaults.claudeBinaryPath   │
                      └─────────────────────────────────┘

                      ┌─────────────────────────────────┐
                      │  packages/cli/commands/setup.ts │ [~]
                      │   detectClaudeExecutablePath()  │
                      │   ├─ ~/.local/bin/claude (native)
                      │   ├─ \$(npm root -g)/.../cli.js  │
                      │   └─ which/where claude         │
                      │   → write CLAUDE_BIN_PATH to    │
                      │      ~/.archon/.env             │
                      └─────────────────────────────────┘

Connection inventory:

From To Status Notes
provider.ts claude-agent-sdk/embed removed The whole embed import is gone
provider.ts binary-resolver.ts new await resolveClaudeBinaryPath() in sendQuery
provider.ts config.ts unchanged still uses parseClaudeConfig
config.ts types.ts modified reads new claudeBinaryPath field
setup.ts binary detection new probes 3 install layouts
setup.ts ~/.archon/.env modified now writes CLAUDE_BIN_PATH line
Dockerfile image env modified pre-sets ENV CLAUDE_BIN_PATH=...
release.yml smoke test new installs Claude Code, runs negative + positive subprocess test

Label Snapshot

  • Risk: risk: medium (breaking change for compiled binary users; well-documented migration; resolver tests + end-to-end smoke verified)
  • Size: size: M (25 files, 751 insertions / 17 deletions; bulk is docs)
  • Scope: core, providers, cli, ci, docs, dependencies
  • Module: providers:claude, cli:setup, ci:release

Change Metadata

Linked Issue

Validation Evidence (required)

bun run type-check     # 10/10 packages: Exited with code 0
bun run lint           # 0 errors, 0 warnings
bun run format:check   # All matched files use Prettier code style
bun run test           # 86 pass / 1 skip / 0 fail across resolver + provider + setup tests
bun run validate       # all of the above

End-to-end smoke testing (against the actual compiled binary):

Check Result
version from compiled binary Build: binary — correct
workflow list from compiled binary 14 workflows discovered, no errors
Real Claude workflow e2e-claude-smoke (3 nodes via SDK) All 3 nodes returned correct output (4, {"category":"math"}, `@archon/providers`)
Resolver negative path (binary, no CLAUDE_BIN_PATH) Clean "Claude Code not found" error with curl/PowerShell/npm install instructions
Resolver positive path (binary, CLAUDE_BIN_PATH=~/.local/bin/claude) Subprocess spawned, returned response, workflow completed
Server /api/health, /api/providers, /api/commands All return correct JSON
Env-leak probe via Archon's full spawn pathway (sentinel .env + .env.local in workflow CWD) Both sentinels arrived UNSET in the spawned bash tool — env protection intact

Smoke testing surfaced one real bug in the initial implementation (--no-env-file was rejected by the native Claude binary) — fixed in commit 0fdf1402.

Security Impact (required)

Compatibility / Migration

  • Backward compatible? No — breaking change for compiled binary users. Source/dev users (bun run) are unaffected.
  • Config/env changes? Yes — new CLAUDE_BIN_PATH env var and assistants.claude.claudeBinaryPath config option.
  • Database migration needed? No

Upgrade steps for compiled-binary users:

# 1. Install Claude Code (Anthropic's recommended native installer)
curl -fsSL https://claude.ai/install.sh | bash      # macOS / Linux / WSL
irm https://claude.ai/install.ps1 | iex             # Windows PowerShell

# 2. Set the path (one of)
export CLAUDE_BIN_PATH="$HOME/.local/bin/claude"   # macOS / Linux
$env:CLAUDE_BIN_PATH = "$env:USERPROFILE\.local\bin\claude.exe"   # Windows

# Or in ~/.archon/config.yaml:
#   assistants:
#     claude:
#       claudeBinaryPath: /absolute/path/to/claude

archon setup auto-detects and writes CLAUDE_BIN_PATH for users who run the wizard. Docker users are unaffected (image pre-sets the env var). npm-installed users can point at $(npm root -g)/@anthropic-ai/claude-code/cli.js.

Human Verification (required)

What was personally validated beyond CI:

  • Verified scenarios:
    • Compiled archon-darwin-arm64 binary on macOS 26.3 (built via scripts/build-binaries.sh TARGET=bun-darwin-arm64 …)
    • Resolver negative path (no env var, no config) → clean install-instruction error
    • Resolver positive path with native curl-installer Claude (~/.local/bin/claude v2.1.107) → workflow completed with correct output
    • Real Claude workflow execution (e2e-claude-smoke — 3 nodes including structured output and tool use) returned correct results
    • Dev-mode CLI (bun run cli) still works without any config
    • End-to-end env-leak probe (sentinel .env + .env.local in workflow CWD via Archon's full spawn pathway) → sentinels arrived UNSET
    • Server starts cleanly on port 3000, all touched endpoints return correct JSON
  • Edge cases checked:
    • --no-env-file flag is now conditional on cliPath?.endsWith('.js') — caught a real bug where the native binary rejected the unknown flag
    • Both env-leak protection layers still active for their respective paths
  • What was not verified:
    • Compiled binaries on Windows / Linux (cross-target builds work but I only have macOS to execute on)
    • npm-installed cli.js path (only verified the native-installer path locally)
    • Behavior with extreme edge-case install layouts (yarn-global, pnpm, custom npm prefix)

The release-workflow smoke test added in this PR exercises the resolver on bun-linux-x64 in CI, covering the platforms I couldn't execute locally.

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: All Claude provider workflow nodes in compiled binaries; setup wizard; release CI; documentation surfaces; Docker image
  • Potential unintended effects:
    • Users upgrading from a working binary will see the new error on first Claude query until they configure CLAUDE_BIN_PATH. Migration is one env var or one YAML line; the error message walks them through it.
    • The --no-env-file conditional change could in theory leave some Bun-runtime use case uncovered. Mitigated by: (a) cliPath?.endsWith('.js') covers dev mode (cliPath undefined) and npm cli.js, (b) end-to-end env-leak probe verified no leak via the production spawn pathway, (c) the parent-process stripCwdEnv() is the load-bearing protection regardless.
  • Guardrails / monitoring for early detection:

Rollback Plan (required)

  • Fast rollback command/path: git revert 33a242e2 0fdf1402 357d8cc6 on dev. Three commits, atomic, fully reversible. No DB changes to roll back.
  • Feature flags or config toggles: None. The change is unconditional.
  • Observable failure symptoms:
    • Compiled-binary Claude workflow nodes fail with Claude Code not found on first run after upgrade if user hasn't configured CLAUDE_BIN_PATH (expected, documented behavior)
    • If the resolver itself regresses, release-workflow smoke test fails before publishing
    • If a Bun-runtime auto-load leak is discovered, --no-env-file can be made unconditional again with a one-line revert of 0fdf1402 plus accepting that native-binary users will see the "unknown option" error

Risks and Mitigations

  • Risk: Some Claude Code install layouts (custom npm prefix, yarn-global, pnpm, monorepo-installed) may not be auto-detected by the setup wizard.
    • Mitigation: Wizard always falls back to a freeform-path prompt. Docs explicitly enumerate the typical paths per install method. Users on exotic layouts already know their filesystem.
  • Risk: Anthropic could change the native installer's binary path away from ~/.local/bin/claude.
    • Mitigation: PATH-resolved which claude is the third probe tier, so any future install path that puts claude on PATH still works. If Anthropic moves away from PATH entirely, that's a much larger problem affecting all SDK consumers.
  • Risk: The --no-env-file conditional change could leak env if a future Bun version's auto-load behavior changes for native binaries.

Summary by CodeRabbit

  • Breaking Changes

    • Compiled Archon binaries no longer bundle Claude Code; install it separately.
  • New Features

    • Add CLAUDE_BIN_PATH env var and assistants.claude.claudeBinaryPath config for compiled binaries.
    • Setup wizard now auto-detects and can persist the Claude executable path.
    • Official Docker image includes Claude Code with path pre-configured.
  • Documentation

    • Expanded install, quick-start, config, and troubleshooting docs with commands and remediation guidance.
  • Bug Fixes

    • Clearer user-facing error and remediation messaging when Claude Code is missing.

Wirasm added 3 commits April 14, 2026 16:12
…esolver

Drop `@anthropic-ai/claude-agent-sdk/embed` and resolve Claude Code via
CLAUDE_BIN_PATH env → assistants.claude.claudeBinaryPath config → throw
with install instructions. The embed's silent failure modes on macOS
(#1210) and Windows (#1087) become actionable errors with a documented
recovery path.

Dev mode (bun run) remains auto-resolved via node_modules. The setup
wizard auto-detects Claude Code by probing the native installer path
(~/.local/bin/claude), npm global cli.js, and PATH, then writes
CLAUDE_BIN_PATH to ~/.archon/.env. Dockerfile pre-sets CLAUDE_BIN_PATH
so extenders using the compiled binary keep working. Release workflow
gets negative and positive resolver smoke tests.

Docs, CHANGELOG, README, .env.example, CLAUDE.md, test-release and
archon skills all updated to reflect the curl-first install story.

Retires #1210, #1087, #1091 (never merged, now obsolete).
Implements #1176.
…Node

`--no-env-file` is a Bun flag that prevents Bun from auto-loading
`.env` from the subprocess cwd. It is only meaningful when the Claude
Code executable is a `cli.js` file — in which case the SDK spawns it
via `bun`/`node` and the flag reaches the runtime.

When `CLAUDE_BIN_PATH` points at a native compiled Claude binary (e.g.
`~/.local/bin/claude` from the curl installer, which is Anthropic's
recommended default), the SDK executes the binary directly. Passing
`--no-env-file` then goes straight to the native binary, which
rejects it with `error: unknown option '--no-env-file'` and the
subprocess exits code 1.

Emit `executableArgs` only when the target is a `.js` file (dev mode
or explicit cli.js path). Caught by end-to-end smoke testing against
the curl-installed native Claude binary.
Verified end-to-end with sentinel `.env` and `.env.local` files in a
workflow CWD that the native Claude binary (curl installer) does not
auto-load `.env` files. With Archon's full spawn pathway and parent
env stripped, the subprocess saw both sentinels as UNSET. The
first-layer protection in `@archon/paths` (#1067) handles the
inheritance leak; `--no-env-file` only matters for the Bun-spawned
cli.js path, where it is still emitted.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

Removes embedded Claude Code SDK from compiled binaries and adds explicit binary-path resolution. Implements a Claude binary resolver, threads optional claudeBinaryPath through setup/config, updates provider to conditionally pass CLI path/--no-env-file, and adds tests, CI smoke tests, Docker env, and docs updates.

Changes

Cohort / File(s) Summary
CLI Setup & Tests
packages/cli/src/commands/setup.ts, packages/cli/src/commands/setup.test.ts
Add ai.claudeBinaryPath handling, auto-detection probes (probeFileExists, probeNpmRoot, probeWhichClaude), detectClaudeExecutablePath(), user prompt for path, and tests for probe order and env emission.
Providers: Resolver, Provider, Types & Tests
packages/providers/src/claude/binary-resolver.ts, packages/providers/src/claude/binary-resolver.test.ts, packages/providers/src/claude/binary-resolver-dev.test.ts, packages/providers/src/claude/provider.ts, packages/providers/src/claude/config.ts, packages/providers/src/types.ts, packages/providers/src/claude/provider.test.ts, packages/providers/src/index.ts
New Claude binary resolver with env→config precedence, existence checks, logging, and dev-mode behavior; integrate resolver into provider (conditional --no-env-file and optional path), extend types/config parsing, add tests, and adjust exports.
Docs & Guides
packages/docs-web/src/content/docs/..., CLAUDE.md, README.md, .claude/skills/.../setup.md, .claude/skills/test-release/SKILL.md, .env.example, CHANGELOG.md
Document that Claude Code is not bundled, add CLAUDE_BIN_PATH and assistants.claude.claudeBinaryPath guidance, show dev vs compiled behavior, install instructions, troubleshooting, and changelog entry.
CI & Release Workflow
.github/workflows/release.yml, packages/providers/package.json
Add Linux smoke-test steps validating resolver/subprocess behavior for bun-linux-x64 and include resolver tests in providers test script.
Docker
Dockerfile, packages/docs-web/src/content/docs/deployment/docker.md
Set CLAUDE_BIN_PATH in production image and document that the official image ships with Claude Code installed.
Quick Start / Local docs edits
packages/docs-web/src/content/docs/getting-started/quick-start.md, .../local.md, .../deployment/docker.md
Adjust prerequisites and ordering to include Claude Code install/config for compiled binaries.
Repo-level test & examples
.claude/skills/test-release/SKILL.md, .claude/skills/archon/guides/setup.md
Add test and setup guidance for explicit Claude Code binary path, detection behavior, and resolver-related pass/fail criteria.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Setup as "archon setup"
    participant Detector as "Detect (probeNpmRoot / which / file)"
    participant Config as "Config (~/.archon/config.yaml)"
    participant Provider as "Claude Provider"
    participant Resolver as "resolveClaudeBinaryPath"
    participant CLI as "Claude Code CLI"

    User->>Setup: run setup
    Setup->>Detector: detect Claude CLI path
    Detector-->>Detector: check platform default\ncheck npm root\ncheck which/where
    Detector-->>Setup: return path or null
    Setup->>User: confirm or input path
    Setup->>Config: write CLAUDE_BIN_PATH or claudeBinaryPath

    User->>Provider: send Claude query
    Provider->>Resolver: resolveClaudeBinaryPath(configClaudeBinaryPath)
    Resolver-->>Resolver: check BUNDLED_IS_BINARY\ncheck CLAUDE_BIN_PATH\ncheck config path\nvalidate fileExists
    Resolver-->>Provider: return path or throw error
    Provider->>CLI: spawn subprocess (with path or let SDK resolve + conditional --no-env-file)
    CLI-->>Provider: response or error
    Provider-->>User: result or remediation message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I sniffed the binary, it wasn’t inside,

I hopped through npm roots and paths far and wide.
Setup now finds, or asks where it’s stored,
CLAUDE_BIN_PATH lights the way to the board,
A little rabbit guiding the CLI tide.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: replacing the embedded Claude SDK path resolution with an explicit binary-path resolver, which is the core fix addressing the silent failures on macOS and Windows.
Description check ✅ Passed PR description includes all required sections with detailed context on problem, motivation, architecture changes, validation, security, compatibility, and rollback.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/claude-binary-resolver-drop-embed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Wirasm
Copy link
Copy Markdown
Collaborator Author

Wirasm commented Apr 14, 2026

PR Review Summary (5 agents)

Ran code-reviewer, docs-impact, pr-test-analyzer, silent-failure-hunter, type-design-analyzer in parallel.

Critical Issues

None. Core resolver (env → config → throw) is correct, fail-fast, well-scoped, end-to-end validated.

Important Issues

Agent Issue Location
pr-test-analyzer (8/10) --no-env-file conditional (isJsExecutable) has no test for the native-binary path — only the cliPath === undefined branch is exercised. A logic flip would regress silently. packages/providers/src/claude/provider.ts:532-542
pr-test-analyzer (7/10) detectClaudeExecutablePath probe order is untested. If native-path probe breaks, wizard silently falls through to a worse tier. packages/cli/src/commands/setup.ts:163-213
code-reviewer (88) resolveClaudeBinaryPath is async but contains zero await. Callers already await, so dropping async/Promise is non-breaking. packages/providers/src/claude/binary-resolver.ts:58
docs-impact CLAUDE.md placeholder shows /absolute/path/to/cli.js and comment says "cli.js path" — native binary is now the primary recommendation. CLAUDE.md:471
docs-impact .env.example npm-alternative line is missing the # prefix — would be parsed as a live assignment. .env.example (CLAUDE_BIN_PATH npm example)

Suggestions

Agent Suggestion Location
silent-failure-hunter Install-instruction throw surfaces as a generic "node failed" in dag-executor. A classifyError branch (like classifyIsolationError) could route setup errors more prominently. packages/workflows/src/dag-executor.ts:1023
silent-failure-hunter Add log.debug({ cliPath, isJsExecutable }, 'claude.subprocess_env_file_flag') so the security-adjacent decision is auditable. packages/providers/src/claude/provider.ts:532
silent-failure-hunter .env writes in setup use plain writeFileSync — non-atomic, crash-during-write leaves truncated credentials file. Use writeFile + rename pattern. packages/cli/src/commands/setup.ts:1297-1301
type-design-analyzer claudeBinaryPath?: string doesn't enforce absoluteness. Add isAbsolute() guard in parseClaudeConfig so config errors surface at load, not at first query. packages/providers/src/claude/config.ts:1044-1046

Strengths

  • Resolver is a pure function with exhaustive tests (env/config/throw × binary/dev).
  • resolvedCliPath computed once before retry loop — consistent first-failure surface.
  • config.ts path parsing guarded by typeof === 'string' — no silent coercion.
  • CI smoke test in release.yml covers the real blast radius (compiled binary on bun-linux-x64) that unit tests can't reach.
  • detectClaudeExecutablePath's empty catch {} blocks are intentional and contract-correct — probe failures are expected "not installed" signals, not swallowed errors.
  • Precedence invariant (env over config) explicitly tested.

Documentation Issues

Two fixes inside files the PR already touched (CLAUDE.md, .env.example) — see Important table above. All other touched docs (README.md, docs-web/**, CHANGELOG.md, Dockerfile, skills) are consistent with resolver behavior.

Verdict: READY TO MERGE WITH MINOR POLISH

No correctness defects. No silent failures. The two Important test gaps and two doc nits are worth addressing in this PR for long-term maintainability, but none block merge.

Recommended Actions

  1. Fix the two doc nits (CLAUDE.md placeholder, .env.example # prefix) — one-line edits.
  2. Drop async from resolveClaudeBinaryPath — mechanical cleanup.
  3. Add provider test for --no-env-file conditional with a native-binary path and a .js path — highest-value test gap.
  4. (Optional) Add detectClaudeExecutablePath probe-order unit tests.
  5. (Optional, follow-up) Atomic .env write; isAbsolute guard in parseClaudeConfig.

Wirasm added 2 commits April 14, 2026 16:38
Final-sweep cleanup tied to the binary-resolver PR:

- Mirror Codex's package surface for the new Claude resolver: add
  `./claude/binary-resolver` subpath export and re-export
  `resolveClaudeBinaryPath` + `claudeFileExists` from the package
  index. Renames the previously single `fileExists` re-export to
  `codexFileExists` for symmetry; nothing outside the providers
  package was importing it.
- Add a "Claude Code not found" entry to the troubleshooting reference
  doc with platform-specific install snippets and pointers to the
  AI Assistants binary-path section.
- Reframe the example claudeBinaryPath in reference/configuration.md
  away from cli.js-only language; it accepts either the native binary
  or cli.js.
Two test gaps and one doc nit from the PR review (#1217):

- Extract the `--no-env-file` decision into a pure exported helper
  `shouldPassNoEnvFile(cliPath)` so the native-binary branch is unit
  testable without mocking `BUNDLED_IS_BINARY` or running the full
  sendQuery pathway. Six new tests cover undefined, cli.js, native
  binary (Linux + Windows), Homebrew symlink, and suffix-only matching.
  Also adds a `claude.subprocess_env_file_flag` debug log so the
  security-adjacent decision is auditable.

- Extract the three install-location probes in setup.ts into exported
  wrappers (`probeFileExists`, `probeNpmRoot`, `probeWhichClaude`) and
  export `detectClaudeExecutablePath` itself, so the probe order can be
  spied on. Six new tests cover each tier winning, fall-through
  ordering, npm-tier skip when not installed, and the
  which-resolved-but-stale-path edge case.

- CLAUDE.md `claudeBinaryPath` placeholder updated to reflect that the
  field accepts either the native binary or cli.js (the example value
  was previously `/absolute/path/to/cli.js`, slightly misleading now
  that the curl-installer native binary is the default).

Skipped from the review by deliberate scope decision:

- `resolveClaudeBinaryPath` async-with-no-await: matches Codex's
  resolver signature exactly. Changing only Claude breaks symmetry;
  if pursued, do both providers in a separate cleanup PR.
- `isAbsolute()` validation in parseClaudeConfig: Codex doesn't do it
  either. Resolver throws on non-existence already.
- Atomic `.env` writes in setup wizard: pre-existing pattern this PR
  touched only adjacently. File as separate issue if needed.
- classifyError branch in dag-executor for setup errors: scope creep.
- `.env.example` "missing #" claim: false positive (verified all
  CLAUDE_BIN_PATH lines have proper comment prefixes).
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/skills/archon/guides/setup.md:
- Line 122: Update the Note about CLAUDE_BIN_PATH in the Archon setup guide:
clarify that CLAUDE_BIN_PATH can point to either the Claude Code SDK's cli.js or
a native claude binary, and that the archon setup wizard (Step 4 in `archon
setup`) performs broader detection (not only `npm root -g`) and resolves common
global and system paths before writing the resolved path to `~/.archon/.env`;
retain the remark that source installs (e.g., `bun run`) do not require setting
CLAUDE_BIN_PATH because the SDK finds `cli.js` via `node_modules`.

In @.claude/skills/test-release/SKILL.md:
- Line 251: Update the pass criteria text in SKILL.md to match the actual error
string emitted by INSTALL_INSTRUCTIONS in binary-resolver.ts: change "Claude
Code CLI not found" to "Claude Code not found" (or alternatively update
INSTALL_INSTRUCTIONS to include "CLI" if you prefer consistency the other way);
locate the INSTALL_INSTRUCTIONS symbol in binary-resolver.ts and the pass
criteria sentence in SKILL.md and make the text identical across both files.

In `@Dockerfile`:
- Around line 111-117: The Dockerfile sets ENV CLAUDE_BIN_PATH to the wrong
package path; change the environment variable CLAUDE_BIN_PATH so it points to
the Claude Code package's cli.js (i.e.,
/app/node_modules/@anthropic-ai/claude-code/cli.js) instead of
/app/node_modules/@anthropic-ai/claude-agent-sdk/cli.js, ensuring the Docker
image and any compiled Archon binary will resolve the correct executable
referenced by binary-resolver.ts and the CHANGELOG.

In `@packages/docs-web/src/content/docs/deployment/docker.md`:
- Around line 16-17: Update the CLAUDE_BIN_PATH guidance to mention that it can
point to either a mounted cli.js or a native Claude executable (not just
cli.js); modify the sentence referencing CLAUDE_BIN_PATH in the
docs/deployment/docker.md content so it explains both supported resolver binary
types and links to the existing Binary path configuration section, ensuring
CLAUDE_BIN_PATH is described as accepting the path to the appropriate
binary/executable for the resolver.

In `@packages/docs-web/src/content/docs/reference/configuration.md`:
- Around line 63-65: The doc currently restricts claudeBinaryPath to cli.js;
update the wording for the claudeBinaryPath configuration entry to indicate it
accepts an executable path (either the cli.js or a native binary) rather than
“cli.js”-only phrasing, and adjust the example/comment text to reflect
“executable path to the Claude Code SDK binary or cli.js” so users know native
binary paths are supported; edit the lines describing claudeBinaryPath in the
configuration.md file to make this change.

In `@packages/providers/src/claude/binary-resolver.test.ts`:
- Around line 79-90: The test currently reuses the same promise returned by
resolver.resolveClaudeBinaryPath() across multiple await expect(...).rejects
assertions (in test 'throws with install instructions when nothing configured'),
which is an anti-pattern in Bun; instead call resolver.resolveClaudeBinaryPath()
once, await it inside a try/catch (or capture the rejection via const err =
await resolver.resolveClaudeBinaryPath().catch(e => e)), then run multiple
synchronous assertions against err.message to check it contains 'Claude Code not
found', 'CLAUDE_BIN_PATH', 'https://claude.ai/install.sh', 'npm install -g
`@anthropic-ai/claude-code`', and 'claudeBinaryPath' so you only consume the
promise once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d28a2bd7-e805-4a62-8a04-31555ae53d55

📥 Commits

Reviewing files that changed from the base of the PR and between 33d31c4 and 357d8cc.

📒 Files selected for processing (25)
  • .claude/skills/archon/guides/setup.md
  • .claude/skills/test-release/SKILL.md
  • .env.example
  • .github/workflows/release.yml
  • CHANGELOG.md
  • CLAUDE.md
  • Dockerfile
  • README.md
  • packages/cli/src/commands/setup.test.ts
  • packages/cli/src/commands/setup.ts
  • packages/docs-web/src/content/docs/deployment/docker.md
  • packages/docs-web/src/content/docs/deployment/local.md
  • packages/docs-web/src/content/docs/getting-started/ai-assistants.md
  • packages/docs-web/src/content/docs/getting-started/configuration.md
  • packages/docs-web/src/content/docs/getting-started/installation.md
  • packages/docs-web/src/content/docs/getting-started/overview.md
  • packages/docs-web/src/content/docs/getting-started/quick-start.md
  • packages/docs-web/src/content/docs/reference/configuration.md
  • packages/providers/package.json
  • packages/providers/src/claude/binary-resolver-dev.test.ts
  • packages/providers/src/claude/binary-resolver.test.ts
  • packages/providers/src/claude/binary-resolver.ts
  • packages/providers/src/claude/config.ts
  • packages/providers/src/claude/provider.ts
  • packages/providers/src/types.ts

3. Verify: `archon version`
4. Check Claude is installed: `which claude`, then `claude /login` if needed

> **Note — Claude Code binary path.** Archon does not bundle Claude Code. In compiled Archon binaries (quick install, Homebrew), the Claude Code SDK needs `CLAUDE_BIN_PATH` set to the absolute path of its `cli.js`. The `archon setup` wizard in Step 4 auto-detects this via `npm root -g` and writes it to `~/.archon/.env` — no manual action needed in the typical case. Source installs (`bun run`) don't need this; the SDK finds `cli.js` via `node_modules` automatically.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update setup note to reflect full resolver/detection behavior.

Line 122 still frames CLAUDE_BIN_PATH as cli.js-only and cites only npm root -g. The current flow supports native claude binary paths too and setup detection is broader.

Suggested wording update
-> **Note — Claude Code binary path.** Archon does not bundle Claude Code. In compiled Archon binaries (quick install, Homebrew), the Claude Code SDK needs `CLAUDE_BIN_PATH` set to the absolute path of its `cli.js`. The `archon setup` wizard in Step 4 auto-detects this via `npm root -g` and writes it to `~/.archon/.env` — no manual action needed in the typical case. Source installs (`bun run`) don't need this; the SDK finds `cli.js` via `node_modules` automatically.
+> **Note — Claude Code binary path.** Archon does not bundle Claude Code. In compiled Archon binaries (quick install, Homebrew), set `CLAUDE_BIN_PATH` to the Claude executable path (native `claude` binary or npm `cli.js`). The `archon setup` wizard in Step 4 auto-detects common locations and writes it to `~/.archon/.env` in typical setups. Source installs (`bun run`) usually don't need this because resolution can use `node_modules`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> **Note — Claude Code binary path.** Archon does not bundle Claude Code. In compiled Archon binaries (quick install, Homebrew), the Claude Code SDK needs `CLAUDE_BIN_PATH` set to the absolute path of its `cli.js`. The `archon setup` wizard in Step 4 auto-detects this via `npm root -g` and writes it to `~/.archon/.env` — no manual action needed in the typical case. Source installs (`bun run`) don't need this; the SDK finds `cli.js` via `node_modules` automatically.
> **Note — Claude Code binary path.** Archon does not bundle Claude Code. In compiled Archon binaries (quick install, Homebrew), set `CLAUDE_BIN_PATH` to the Claude executable path (native `claude` binary or npm `cli.js`). The `archon setup` wizard in Step 4 auto-detects common locations and writes it to `~/.archon/.env` in typical setups. Source installs (`bun run`) usually don't need this because resolution can use `node_modules`.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/skills/archon/guides/setup.md at line 122, Update the Note about
CLAUDE_BIN_PATH in the Archon setup guide: clarify that CLAUDE_BIN_PATH can
point to either the Claude Code SDK's cli.js or a native claude binary, and that
the archon setup wizard (Step 4 in `archon setup`) performs broader detection
(not only `npm root -g`) and resolves common global and system paths before
writing the resolved path to `~/.archon/.env`; retain the remark that source
installs (e.g., `bun run`) do not require setting CLAUDE_BIN_PATH because the
SDK finds `cli.js` via `node_modules`.


- Exit code 0
- The Claude subprocess spawns successfully (no `spawn EACCES`, `ENOENT`, or `process exited with code 1` in the early output)
- No `Claude Code CLI not found` error (that means the resolver rejected the configured path — verify the cli.js actually exists)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor text inconsistency with actual error message.

The pass criteria references "Claude Code CLI not found" but the actual error message from INSTALL_INSTRUCTIONS in binary-resolver.ts is "Claude Code not found" (without "CLI"). Consider aligning for consistency.

📝 Suggested fix
-- No `Claude Code CLI not found` error (that means the resolver rejected the configured path — verify the cli.js actually exists)
+- No `Claude Code not found` error (that means the resolver rejected the configured path — verify the cli.js actually exists)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- No `Claude Code CLI not found` error (that means the resolver rejected the configured path — verify the cli.js actually exists)
- No `Claude Code not found` error (that means the resolver rejected the configured path — verify the cli.js actually exists)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/skills/test-release/SKILL.md at line 251, Update the pass criteria
text in SKILL.md to match the actual error string emitted by
INSTALL_INSTRUCTIONS in binary-resolver.ts: change "Claude Code CLI not found"
to "Claude Code not found" (or alternatively update INSTALL_INSTRUCTIONS to
include "CLI" if you prefer consistency the other way); locate the
INSTALL_INSTRUCTIONS symbol in binary-resolver.ts and the pass criteria sentence
in SKILL.md and make the text identical across both files.

Comment thread Dockerfile
Comment on lines +111 to +117
# Pre-configure the Claude Code SDK cli.js path for any consumer that runs
# a compiled Archon binary inside (or extending) this image. In source mode
# (the default `bun run start` ENTRYPOINT), BUNDLED_IS_BINARY is false and
# this variable is ignored — the SDK resolves cli.js via node_modules. Kept
# here so extenders don't need to rediscover the path.
# Path matches the hoisted layout produced by `bun install --linker=hoisted`.
ENV CLAUDE_BIN_PATH=/app/node_modules/@anthropic-ai/claude-agent-sdk/cli.js
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check which Claude package is actually installed and contains cli.js
echo "=== Checking package.json dependencies ==="
rg -n "claude" package.json packages/providers/package.json

echo ""
echo "=== Looking for cli.js references in codebase ==="
rg -n "claude.*cli\.js" --type ts

Repository: coleam00/Archon

Length of output: 2302


🏁 Script executed:

# Let's read the binary-resolver.ts file to understand the context
head -60 packages/providers/src/claude/binary-resolver.ts

Repository: coleam00/Archon

Length of output: 2643


🏁 Script executed:

# Also check the Dockerfile context around the line in question
head -120 Dockerfile | tail -20

Repository: coleam00/Archon

Length of output: 1048


🏁 Script executed:

# Search for any package.json or documentation that mentions both package names
rg -n "claude-code|claude-agent-sdk" --type json --type md | head -30

Repository: coleam00/Archon

Length of output: 4716


Fix Dockerfile: CLAUDE_BIN_PATH must point to @anthropic-ai/claude-code, not @anthropic-ai/claude-agent-sdk.

The Dockerfile sets ENV CLAUDE_BIN_PATH=/app/node_modules/@anthropic-ai/claude-agent-sdk/cli.js, but this is incorrect. Per the CHANGELOG and source code, @anthropic-ai/claude-agent-sdk is the SDK package that Archon uses; it internally spawns a separate Claude Code executable. The cli.js file belongs to @anthropic-ai/claude-code, not the SDK.

All documentation, install instructions in binary-resolver.ts, and the CHANGELOG consistently reference @anthropic-ai/claude-code/cli.js as the correct path. Update line 117 to:

ENV CLAUDE_BIN_PATH=/app/node_modules/@anthropic-ai/claude-code/cli.js

Note: @anthropic-ai/claude-code is not currently listed in the Dockerfile's or root package.json dependencies, but it will be installed as a peer dependency or must be installed separately in compiled binary deployments (as documented in binary-resolver.ts).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 111 - 117, The Dockerfile sets ENV CLAUDE_BIN_PATH
to the wrong package path; change the environment variable CLAUDE_BIN_PATH so it
points to the Claude Code package's cli.js (i.e.,
/app/node_modules/@anthropic-ai/claude-code/cli.js) instead of
/app/node_modules/@anthropic-ai/claude-agent-sdk/cli.js, ensuring the Docker
image and any compiled Archon binary will resolve the correct executable
referenced by binary-resolver.ts and the CHANGELOG.

Comment on lines +16 to +17
> required. If you build a custom image that omits the npm install, set `CLAUDE_BIN_PATH` yourself
> to point at a mounted `cli.js` (see [AI Assistants → Binary path configuration](/getting-started/ai-assistants/#binary-path-configuration-compiled-binaries-only)).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Broaden CLAUDE_BIN_PATH guidance beyond cli.js.

Line 16-Line 17 currently implies custom images must point to a mounted cli.js. The resolver now supports native Claude executables too, so this wording is too restrictive and may mislead users.

Suggested doc tweak
-> required. If you build a custom image that omits the npm install, set `CLAUDE_BIN_PATH` yourself
-> to point at a mounted `cli.js` (see [AI Assistants → Binary path configuration](/getting-started/ai-assistants/#binary-path-configuration-compiled-binaries-only)).
+> required. If you build a custom image that omits the npm install, set `CLAUDE_BIN_PATH` yourself
+> to the Claude executable path (native `claude` binary or npm `cli.js`; see [AI Assistants → Binary path configuration](/getting-started/ai-assistants/#binary-path-configuration-compiled-binaries-only)).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> required. If you build a custom image that omits the npm install, set `CLAUDE_BIN_PATH` yourself
> to point at a mounted `cli.js` (see [AI Assistants → Binary path configuration](/getting-started/ai-assistants/#binary-path-configuration-compiled-binaries-only)).
> required. If you build a custom image that omits the npm install, set `CLAUDE_BIN_PATH` yourself
> to the Claude executable path (native `claude` binary or npm `cli.js`; see [AI Assistants → Binary path configuration](/getting-started/ai-assistants/#binary-path-configuration-compiled-binaries-only)).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs-web/src/content/docs/deployment/docker.md` around lines 16 -
17, Update the CLAUDE_BIN_PATH guidance to mention that it can point to either a
mounted cli.js or a native Claude executable (not just cli.js); modify the
sentence referencing CLAUDE_BIN_PATH in the docs/deployment/docker.md content so
it explains both supported resolver binary types and links to the existing
Binary path configuration section, ensuring CLAUDE_BIN_PATH is described as
accepting the path to the appropriate binary/executable for the resolver.

Comment thread packages/docs-web/src/content/docs/reference/configuration.md Outdated
Comment on lines +79 to +90
test('throws with install instructions when nothing configured', async () => {
fileExistsSpy = spyOn(resolver, 'fileExists').mockReturnValue(false);

const promise = resolver.resolveClaudeBinaryPath();
await expect(promise).rejects.toThrow('Claude Code not found');
await expect(promise).rejects.toThrow('CLAUDE_BIN_PATH');
// Native curl installer is Anthropic's primary recommendation.
await expect(promise).rejects.toThrow('https://claude.ai/install.sh');
// npm path is still documented as an alternative.
await expect(promise).rejects.toThrow('npm install -g @anthropic-ai/claude-code');
await expect(promise).rejects.toThrow('claudeBinaryPath');
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify how Bun handles multiple await expect().rejects on the same promise
cd packages/providers
bun test src/claude/binary-resolver.test.ts --timeout 30000 2>&1 | head -50

Repository: coleam00/Archon

Length of output: 100


🏁 Script executed:

cat -n packages/providers/src/claude/binary-resolver.test.ts

Repository: coleam00/Archon

Length of output: 4266


🏁 Script executed:

# Search for how similar patterns are used in other test files
fd -e test.ts -o -e test.tsx | head -20 | xargs -I {} sh -c 'grep -l "await expect.*rejects" {} 2>/dev/null' | head -5

Repository: coleam00/Archon

Length of output: 175


🌐 Web query:

Bun test expect().rejects.toThrow() multiple calls same promise

💡 Result:

In Bun test (as of 2026-04-14), you cannot reliably make multiple expect.rejects.toThrow calls on the same promise. The expect(promise).rejects matcher awaits and consumes the promise synchronously during the first assertion, so subsequent calls on the same promise will fail because the promise is already settled. Official Bun documentation shows examples like expect(Promise.reject(new Error)).rejects.toBeInstanceOf(Error) (single matcher) and supports chaining one matcher after .rejects or .resolves, such as .rejects.toBe, .rejects.toThrow, etc. However, no examples or documentation support multiple separate expect chains on the same promise instance. Bun's implementation differs from Jest: Bun's promise matchers block synchronously until settlement (see issue discussions), preventing reuse. Workarounds for multiple assertions on the rejection reason: 1. Use expect.assertions(n) with try-catch: test('multiple checks', async => { expect.assertions(2); try { await rejectingPromise; } catch (err) { expect(err).toBeInstanceOf(Error); expect(err.message).toMatch(/expected/); } }); 2. Single chain if possible, e.g., expect(promise).rejects.toSatisfy(err => { expect(err.message).toBe('foo'); return true; }) (but Bun may pass promise instead of value in some matchers like toSatisfy). 3. Create separate promises or use test.each for multiple cases. Some .rejects.toThrow combinations have historical bugs (e.g., issues #5602, #4909), but basic .rejects.toBe(errorValue) works per docs and PR #3318. For Jest compatibility, avoid reusing the same promise across multiple expects.

Citations:


Rewrite multiple await expect() calls to avoid promise reuse anti-pattern in Bun tests.

Bun's expect(promise).rejects matcher consumes the promise on the first assertion, making subsequent calls on the same promise instance unreliable. The test should capture the error once and assert on the message:

Suggested fix
  test('throws with install instructions when nothing configured', async () => {
-   fileExistsSpy = spyOn(resolver, 'fileExists').mockReturnValue(false);
-
-   const promise = resolver.resolveClaudeBinaryPath();
-   await expect(promise).rejects.toThrow('Claude Code not found');
-   await expect(promise).rejects.toThrow('CLAUDE_BIN_PATH');
-   // Native curl installer is Anthropic's primary recommendation.
-   await expect(promise).rejects.toThrow('https://claude.ai/install.sh');
-   // npm path is still documented as an alternative.
-   await expect(promise).rejects.toThrow('npm install -g `@anthropic-ai/claude-code`');
-   await expect(promise).rejects.toThrow('claudeBinaryPath');
+   fileExistsSpy = spyOn(resolver, 'fileExists').mockReturnValue(false);
+
+   let error: Error | undefined;
+   try {
+     await resolver.resolveClaudeBinaryPath();
+   } catch (e) {
+     error = e as Error;
+   }
+   expect(error).toBeDefined();
+   expect(error!.message).toContain('Claude Code not found');
+   expect(error!.message).toContain('CLAUDE_BIN_PATH');
+   // Native curl installer is Anthropic's primary recommendation.
+   expect(error!.message).toContain('https://claude.ai/install.sh');
+   // npm path is still documented as an alternative.
+   expect(error!.message).toContain('npm install -g `@anthropic-ai/claude-code`');
+   expect(error!.message).toContain('claudeBinaryPath');
  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/providers/src/claude/binary-resolver.test.ts` around lines 79 - 90,
The test currently reuses the same promise returned by
resolver.resolveClaudeBinaryPath() across multiple await expect(...).rejects
assertions (in test 'throws with install instructions when nothing configured'),
which is an anti-pattern in Bun; instead call resolver.resolveClaudeBinaryPath()
once, await it inside a try/catch (or capture the rejection via const err =
await resolver.resolveClaudeBinaryPath().catch(e => e)), then run multiple
synchronous assertions against err.message to check it contains 'Claude Code not
found', 'CLAUDE_BIN_PATH', 'https://claude.ai/install.sh', 'npm install -g
`@anthropic-ai/claude-code`', and 'claudeBinaryPath' so you only consume the
promise once.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/docs-web/src/content/docs/reference/troubleshooting.md (1)

287-290: Add language specifier to fenced code block.

The code block showing the error message is missing a language identifier, which triggers a markdownlint warning (MD040). Use text or plaintext for non-code output.

📝 Proposed fix
-```
+```text
 Claude Code not found. Archon requires the Claude Code executable to be
 reachable at a configured path in compiled builds.
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @packages/docs-web/src/content/docs/reference/troubleshooting.md around lines
287 - 290, The fenced code block containing the error message "Claude Code not
found. Archon requires the Claude Code executable to be reachable at a
configured path in compiled builds." is missing a language specifier and
triggers markdownlint MD040; update that fenced block in
packages/docs-web/src/content/docs/reference/troubleshooting.md to use a
plain-text specifier (e.g., text or plaintext) so the block is marked as
non-code output and the lint warning is resolved.


</details>

</blockquote></details>
<details>
<summary>packages/cli/src/commands/setup.ts (1)</summary><blockquote>

`473-491`: **Consider validating that user-provided path is absolute.**

The prompt asks for an "Absolute path" but doesn't validate that the input is actually absolute. Relative paths would be resolved against the current working directory at runtime, which may differ from where setup was run.


<details>
<summary>🛡️ Proposed validation</summary>

```diff
+import { isAbsolute } from 'path';
+
   const trimmed = (customPath ?? '').trim();
   if (!trimmed) return undefined;
 
+  if (!isAbsolute(trimmed)) {
+    log.warning(
+      `Path "${trimmed}" is not absolute. Please provide a full path starting from root.`
+    );
+    return undefined;
+  }
+
   if (!existsSync(trimmed)) {
     log.warning(
```
</details>

Note: `isAbsolute` is already imported from `path` at line 26, so the import addition is not needed.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/setup.ts` around lines 473 - 491, The prompt
accepts a "Absolute path" but doesn't validate it; update the logic after
computing trimmed (from customPath) to check path.isAbsolute(trimmed) and handle
non-absolute input: if !isAbsolute(trimmed) log a clear warning/error (using
log.warning or similar) telling the user the path must be absolute and return
undefined (or re-prompt if desired) instead of accepting a relative path; keep
the existing existsSync(trimmed) check for existence after the absolute check so
the code that references trimmed only gets absolute paths.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @packages/cli/src/commands/setup.ts:

  • Around line 473-491: The prompt accepts a "Absolute path" but doesn't validate
    it; update the logic after computing trimmed (from customPath) to check
    path.isAbsolute(trimmed) and handle non-absolute input: if !isAbsolute(trimmed)
    log a clear warning/error (using log.warning or similar) telling the user the
    path must be absolute and return undefined (or re-prompt if desired) instead of
    accepting a relative path; keep the existing existsSync(trimmed) check for
    existence after the absolute check so the code that references trimmed only gets
    absolute paths.

In @packages/docs-web/src/content/docs/reference/troubleshooting.md:

  • Around line 287-290: The fenced code block containing the error message
    "Claude Code not found. Archon requires the Claude Code executable to be
    reachable at a configured path in compiled builds." is missing a language
    specifier and triggers markdownlint MD040; update that fenced block in
    packages/docs-web/src/content/docs/reference/troubleshooting.md to use a
    plain-text specifier (e.g., text or plaintext) so the block is marked as
    non-code output and the lint warning is resolved.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `20c1c99d-0a1a-4622-a7b9-ea95b98f02f7`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 357d8cc695d51f81381bf0a43a94b6093dfaef00 and 44ec6e3023b20e1d2abaa6efbe34fc6a056b200d.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `CLAUDE.md`
* `packages/cli/src/commands/setup.test.ts`
* `packages/cli/src/commands/setup.ts`
* `packages/docs-web/src/content/docs/reference/configuration.md`
* `packages/docs-web/src/content/docs/reference/troubleshooting.md`
* `packages/providers/package.json`
* `packages/providers/src/claude/provider.test.ts`
* `packages/providers/src/claude/provider.ts`
* `packages/providers/src/index.ts`

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (2)</summary>

* CLAUDE.md
* packages/docs-web/src/content/docs/reference/configuration.md

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* packages/providers/package.json
* packages/cli/src/commands/setup.test.ts

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

The "tier 2 wins (npm cli.js)" test hardcoded forward-slash path
comparisons, but `path.join` produces backslashes on Windows. Caused
the Windows CI leg of the test suite to fail while macOS and Linux
passed. Use `path.join` for both the mock return value and the
expectation so the separator matches whatever the platform produces.
@Wirasm Wirasm merged commit 81859d6 into dev Apr 14, 2026
4 checks passed
@Wirasm Wirasm deleted the fix/claude-binary-resolver-drop-embed branch April 14, 2026 14:56
joaobmonteiro pushed a commit to joaobmonteiro/Archon that referenced this pull request Apr 26, 2026
…solver (coleam00#1217)

* feat(providers): replace Claude SDK embed with explicit binary-path resolver

Drop `@anthropic-ai/claude-agent-sdk/embed` and resolve Claude Code via
CLAUDE_BIN_PATH env → assistants.claude.claudeBinaryPath config → throw
with install instructions. The embed's silent failure modes on macOS
(coleam00#1210) and Windows (coleam00#1087) become actionable errors with a documented
recovery path.

Dev mode (bun run) remains auto-resolved via node_modules. The setup
wizard auto-detects Claude Code by probing the native installer path
(~/.local/bin/claude), npm global cli.js, and PATH, then writes
CLAUDE_BIN_PATH to ~/.archon/.env. Dockerfile pre-sets CLAUDE_BIN_PATH
so extenders using the compiled binary keep working. Release workflow
gets negative and positive resolver smoke tests.

Docs, CHANGELOG, README, .env.example, CLAUDE.md, test-release and
archon skills all updated to reflect the curl-first install story.

Retires coleam00#1210, coleam00#1087, coleam00#1091 (never merged, now obsolete).
Implements coleam00#1176.

* fix(providers): only pass --no-env-file when spawning Claude via Bun/Node

`--no-env-file` is a Bun flag that prevents Bun from auto-loading
`.env` from the subprocess cwd. It is only meaningful when the Claude
Code executable is a `cli.js` file — in which case the SDK spawns it
via `bun`/`node` and the flag reaches the runtime.

When `CLAUDE_BIN_PATH` points at a native compiled Claude binary (e.g.
`~/.local/bin/claude` from the curl installer, which is Anthropic's
recommended default), the SDK executes the binary directly. Passing
`--no-env-file` then goes straight to the native binary, which
rejects it with `error: unknown option '--no-env-file'` and the
subprocess exits code 1.

Emit `executableArgs` only when the target is a `.js` file (dev mode
or explicit cli.js path). Caught by end-to-end smoke testing against
the curl-installed native Claude binary.

* docs: record env-leak validation result in provider comment

Verified end-to-end with sentinel `.env` and `.env.local` files in a
workflow CWD that the native Claude binary (curl installer) does not
auto-load `.env` files. With Archon's full spawn pathway and parent
env stripped, the subprocess saw both sentinels as UNSET. The
first-layer protection in `@archon/paths` (coleam00#1067) handles the
inheritance leak; `--no-env-file` only matters for the Bun-spawned
cli.js path, where it is still emitted.

* chore(providers): cleanup pass — exports, docs, troubleshooting

Final-sweep cleanup tied to the binary-resolver PR:

- Mirror Codex's package surface for the new Claude resolver: add
  `./claude/binary-resolver` subpath export and re-export
  `resolveClaudeBinaryPath` + `claudeFileExists` from the package
  index. Renames the previously single `fileExists` re-export to
  `codexFileExists` for symmetry; nothing outside the providers
  package was importing it.
- Add a "Claude Code not found" entry to the troubleshooting reference
  doc with platform-specific install snippets and pointers to the
  AI Assistants binary-path section.
- Reframe the example claudeBinaryPath in reference/configuration.md
  away from cli.js-only language; it accepts either the native binary
  or cli.js.

* test+refactor(providers, cli): address PR review feedback

Two test gaps and one doc nit from the PR review (coleam00#1217):

- Extract the `--no-env-file` decision into a pure exported helper
  `shouldPassNoEnvFile(cliPath)` so the native-binary branch is unit
  testable without mocking `BUNDLED_IS_BINARY` or running the full
  sendQuery pathway. Six new tests cover undefined, cli.js, native
  binary (Linux + Windows), Homebrew symlink, and suffix-only matching.
  Also adds a `claude.subprocess_env_file_flag` debug log so the
  security-adjacent decision is auditable.

- Extract the three install-location probes in setup.ts into exported
  wrappers (`probeFileExists`, `probeNpmRoot`, `probeWhichClaude`) and
  export `detectClaudeExecutablePath` itself, so the probe order can be
  spied on. Six new tests cover each tier winning, fall-through
  ordering, npm-tier skip when not installed, and the
  which-resolved-but-stale-path edge case.

- CLAUDE.md `claudeBinaryPath` placeholder updated to reflect that the
  field accepts either the native binary or cli.js (the example value
  was previously `/absolute/path/to/cli.js`, slightly misleading now
  that the curl-installer native binary is the default).

Skipped from the review by deliberate scope decision:

- `resolveClaudeBinaryPath` async-with-no-await: matches Codex's
  resolver signature exactly. Changing only Claude breaks symmetry;
  if pursued, do both providers in a separate cleanup PR.
- `isAbsolute()` validation in parseClaudeConfig: Codex doesn't do it
  either. Resolver throws on non-existence already.
- Atomic `.env` writes in setup wizard: pre-existing pattern this PR
  touched only adjacently. File as separate issue if needed.
- classifyError branch in dag-executor for setup errors: scope creep.
- `.env.example` "missing #" claim: false positive (verified all
  CLAUDE_BIN_PATH lines have proper comment prefixes).

* fix(test): use path.join in Windows-compatible probe-order test

The "tier 2 wins (npm cli.js)" test hardcoded forward-slash path
comparisons, but `path.join` produces backslashes on Windows. Caused
the Windows CI leg of the test suite to fail while macOS and Linux
passed. Use `path.join` for both the mock return value and the
expectation so the separator matches whatever the platform produces.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant