Summary
On glibc Linux hosts (Ubuntu/Debian/Fedora/etc.), archon workflow run fails immediately when running from source (dev mode). The Claude Code SDK auto-resolves its bundled binary in
[linux-x64-musl, linux-x64] order, so the musl variant is selected first; its ELF interpreter (/lib/ld-musl-x86_64.so.1) doesn't exist on glibc systems, and the spawn fails with a
misleading error that blames a missing path.
The error currently surfaces as:
ReferenceError: Claude Code native binary not found at
.../node_modules/.bun/@anthropic-ai+claude-agent-sdk-linux-x64-musl@0.2.121
/node_modules/@anthropic-ai/claude-agent-sdk-linux-x64-musl/claude
The file is on disk and executable; the spawn failure is from the missing musl loader, not a missing binary.
Reproduction
- Install Archon from source on a glibc host (any current Ubuntu/Debian/Fedora).
bun run dev:server (or just bun /home/$USER/.bun/bin/archon workflow run ...).
- Run any workflow:
archon workflow run archon-assist --branch test "anything".
- Workflow fails in <1s with the error above.
Both @anthropic-ai/claude-agent-sdk-linux-x64-musl and @anthropic-ai/claude-agent-sdk-linux-x64 install via optionalDependencies on Linux (libc field in their package.json doesn't
constrain Bun's install), and the SDK's resolver (function N7 in sdk.mjs) tries musl first.
Setting CLAUDE_BIN_PATH=/home/<user>/.local/bin/claude (the documented escape hatch) does not fix it: binary-resolver.ts early-returns undefined whenever
BUNDLED_IS_BINARY=false, before the env var is even read. There is no working override for dev mode short of patching node_modules.
Proposed fix
Move the CLAUDE_BIN_PATH env-var check above the BUNDLED_IS_BINARY early return in packages/providers/src/claude/binary-resolver.ts. Config-file path stays binary-mode-only (it's
per-repo, not per-machine — the env var is the right knob for libc-mismatch escape).
Behavior preserved:
- env var unset → identical to today (returns
undefined in dev, falls through to autodetect/throw in binary mode)
- env var set + file exists → resolved binary used (was already true in binary mode; now also true in dev mode)
- env var set + file missing → clear error message (was already true in binary mode; now also true in dev mode)
Patch
diff --git a/packages/providers/src/claude/binary-resolver.ts b/packages/providers/src/claude/binary-resolver.ts
index 6b918d44..0658374e 100644
--- a/packages/providers/src/claude/binary-resolver.ts
+++ b/packages/providers/src/claude/binary-resolver.ts
@@ -64,9 +64,9 @@ const INSTALL_INSTRUCTIONS =
export async function resolveClaudeBinaryPath(
configClaudeBinaryPath?: string
): Promise<string | undefined> {
- if (!BUNDLED_IS_BINARY) return undefined;
-
- // 1. Environment variable override
+ // 1. Environment variable override — honored in dev mode too, so operators
+ // on libc mismatches (e.g. glibc host with musl SDK variant first in the
+ // resolution list) can pin a known-good binary without a compiled build.
const envPath = process.env.CLAUDE_BIN_PATH;
if (envPath) {
if (!fileExists(envPath)) {
@@ -80,6 +80,8 @@ export async function resolveClaudeBinaryPath(
return envPath;
}
+ if (!BUNDLED_IS_BINARY) return undefined;
+
// 2. Config file override
if (configClaudeBinaryPath) {
if (!fileExists(configClaudeBinaryPath)) {
Tests
The existing binary-resolver-dev.test.ts asserts the old behavior (returns undefined even with env var set). It needs to be updated to reflect the new contract. Replacement:
/**
* Tests for the Claude binary resolver in dev mode (BUNDLED_IS_BINARY=false).
* Separate file because binary-mode tests mock BUNDLED_IS_BINARY=true.
*
* Dev mode normally lets the SDK resolve the binary from its bundled
* platform package. CLAUDE_BIN_PATH is honored as an escape hatch for
* environments where SDK auto-resolution picks the wrong variant — most
* notably glibc Linux hosts, where the SDK prefers the musl binary first
* and silently falls over with a misleading "not found" error.
* Config-file path is intentionally NOT honored in dev mode (still binary-only).
*/
import { describe, test, expect, mock, beforeEach, afterAll, spyOn } from 'bun:test';
import { createMockLogger } from '../test/mocks/logger';
mock.module('@archon/paths', () => ({
createLogger: mock(() => createMockLogger()),
BUNDLED_IS_BINARY: false,
}));
import * as resolver from './binary-resolver';
describe('resolveClaudeBinaryPath (dev mode)', () => {
const originalEnv = process.env.CLAUDE_BIN_PATH;
let fileExistsSpy: ReturnType<typeof spyOn>;
beforeEach(() => {
delete process.env.CLAUDE_BIN_PATH;
fileExistsSpy?.mockRestore();
});
afterAll(() => {
if (originalEnv !== undefined) {
process.env.CLAUDE_BIN_PATH = originalEnv;
} else {
delete process.env.CLAUDE_BIN_PATH;
}
fileExistsSpy?.mockRestore();
});
test('returns undefined when nothing is configured', async () => {
const result = await resolver.resolveClaudeBinaryPath();
expect(result).toBeUndefined();
});
test('returns undefined when only config path is set (config is binary-mode only)', async () => {
const result = await resolver.resolveClaudeBinaryPath('/some/custom/path');
expect(result).toBeUndefined();
});
test('honors CLAUDE_BIN_PATH env var when file exists', async () => {
process.env.CLAUDE_BIN_PATH = '/usr/local/bin/claude';
fileExistsSpy = spyOn(resolver, 'fileExists').mockReturnValue(true);
const result = await resolver.resolveClaudeBinaryPath();
expect(result).toBe('/usr/local/bin/claude');
});
test('throws when CLAUDE_BIN_PATH is set but file does not exist', async () => {
process.env.CLAUDE_BIN_PATH = '/nonexistent/claude';
fileExistsSpy = spyOn(resolver, 'fileExists').mockReturnValue(false);
await expect(resolver.resolveClaudeBinaryPath()).rejects.toThrow(
'CLAUDE_BIN_PATH is set to "/nonexistent/claude" but the file does not exist'
);
});
test('env var wins over config path in dev mode', async () => {
process.env.CLAUDE_BIN_PATH = '/env/claude';
fileExistsSpy = spyOn(resolver, 'fileExists').mockReturnValue(true);
const result = await resolver.resolveClaudeBinaryPath('/config/claude');
expect(result).toBe('/env/claude');
});
});
Both test files pass locally:
bun test packages/providers/src/claude/binary-resolver-dev.test.ts → 5 pass
bun test packages/providers/src/claude/binary-resolver.test.ts → 9 pass (binary-mode tests, unaffected — env var was already step 1 there)
Environment
- OS: Ubuntu (glibc 2.33)
- Bun: 1.3.13
- Archon: dev branch at
bf1f471e
- Claude Code SDK:
@anthropic-ai/claude-agent-sdk@0.2.121
- Reproduces with
CLAUDECODE=1 set or unset (the nested-Claude warning is unrelated).
Summary
On glibc Linux hosts (Ubuntu/Debian/Fedora/etc.),
archon workflow runfails immediately when running from source (dev mode). The Claude Code SDK auto-resolves its bundled binary in[linux-x64-musl, linux-x64]order, so the musl variant is selected first; its ELF interpreter (/lib/ld-musl-x86_64.so.1) doesn't exist on glibc systems, and the spawn fails with amisleading error that blames a missing path.
The error currently surfaces as:
The file is on disk and executable; the spawn failure is from the missing musl loader, not a missing binary.
Reproduction
bun run dev:server(or justbun /home/$USER/.bun/bin/archon workflow run ...).archon workflow run archon-assist --branch test "anything".Both
@anthropic-ai/claude-agent-sdk-linux-x64-musland@anthropic-ai/claude-agent-sdk-linux-x64install via optionalDependencies on Linux (libc field in theirpackage.jsondoesn'tconstrain Bun's install), and the SDK's resolver (function
N7insdk.mjs) tries musl first.Setting
CLAUDE_BIN_PATH=/home/<user>/.local/bin/claude(the documented escape hatch) does not fix it:binary-resolver.tsearly-returnsundefinedwheneverBUNDLED_IS_BINARY=false, before the env var is even read. There is no working override for dev mode short of patchingnode_modules.Proposed fix
Move the
CLAUDE_BIN_PATHenv-var check above theBUNDLED_IS_BINARYearly return inpackages/providers/src/claude/binary-resolver.ts. Config-file path stays binary-mode-only (it'sper-repo, not per-machine — the env var is the right knob for libc-mismatch escape).
Behavior preserved:
undefinedin dev, falls through to autodetect/throw in binary mode)Patch
Tests
The existing
binary-resolver-dev.test.tsasserts the old behavior (returnsundefinedeven with env var set). It needs to be updated to reflect the new contract. Replacement:Both test files pass locally:
bun test packages/providers/src/claude/binary-resolver-dev.test.ts→ 5 passbun test packages/providers/src/claude/binary-resolver.test.ts→ 9 pass (binary-mode tests, unaffected — env var was already step 1 there)Environment
bf1f471e@anthropic-ai/claude-agent-sdk@0.2.121CLAUDECODE=1set or unset (the nested-Claude warning is unrelated).