resolve the claude cli instead of hardcoding /usr/local/bin/claude

the path was pinned to /usr/local/bin/claude for the bwrap-sandboxed
architecture: the jail ro-bound /usr and saw nothing else, so the
installer's real target (~/.local/bin/claude) had to be symlinked
somewhere the sandbox could reach. that sandbox is gone, and the constant
outlived it — the sidecar could not run anywhere the symlink was absent.
a stock macos host has no /usr/local/bin at all.

resolve at module load instead: CLAUDE_BIN pins it explicitly, else
whatever is on PATH, else the locations the installer writes to. same
shape as OPENCODE_BIN in the opencode sidecar. the resolved path is
logged at startup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-28 14:11:47 +01:00
co-authored by Claude Opus 5
parent 597675d6a0
commit 75ce4ad44f
+23 -3
View File
@@ -1,3 +1,5 @@
import { existsSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { query, type Query } from '@anthropic-ai/claude-agent-sdk';
import type { ChatEvent } from '../../api/chat/types';
@@ -7,9 +9,27 @@ import { createParseState, processMessage } from './stream-parser';
const SEND_TIMEOUT_MS = 30 * 60 * 1000;
// Use /usr/local/bin/claude so it's visible inside bwrap sandbox (which ro-binds /usr).
// The actual binary lives at ~/.local/bin/claude, symlinked from /usr/local/bin/claude.
const CLAUDE_BIN = '/usr/local/bin/claude';
// Where the Claude Code CLI lives. This was hardcoded to /usr/local/bin/claude, which dated from the
// bwrap-sandboxed architecture: the jail ro-bound /usr and saw nothing else, so the installer's real
// target (~/.local/bin/claude) had to be symlinked into a path the sandbox could reach. That sandbox
// is gone, and the hardcoded path made the sidecar unrunnable anywhere it does not exist — a stock
// macOS host has no /usr/local/bin at all.
//
// Resolution order mirrors OPENCODE_BIN in the opencode sidecar: an explicit pin, then PATH, then the
// locations Anthropic's installer actually writes to.
function resolveClaudeBin(): string {
const pinned = process.env.CLAUDE_BIN;
if (pinned) return pinned;
const onPath = Bun.which('claude');
if (onPath) return onPath;
const candidates = [join(homedir(), '.local', 'bin', 'claude'), '/usr/local/bin/claude', '/opt/homebrew/bin/claude'];
return candidates.find((candidate) => existsSync(candidate)) ?? 'claude';
}
const CLAUDE_BIN = resolveClaudeBin();
console.log(`[claude] CLI resolved to ${CLAUDE_BIN}`);
// Capture original HOME before user-instance overrides it
const HOST_HOME = process.env.HOME!;