feat(v2): auto-detect Claude CLI path and pass to SDK via pathToClaudeCodeExecutable

Both sidecar runners (agent-runner.ts and agent-runner-deno.ts) now include
findClaudeCli() which checks common paths (~/.local/bin/claude,
~/.claude/local/claude, /usr/local/bin/claude, /usr/bin/claude) and falls
back to `which claude`. The resolved path is passed to the SDK query()
options as pathToClaudeCodeExecutable. If the CLI is not found, an
agent_error is emitted immediately instead of a cryptic SDK failure.
This commit is contained in:
Hibryda 2026-03-07 01:28:04 +01:00
parent 14b62da729
commit d35b3dc7fc
2 changed files with 68 additions and 0 deletions

View file

@ -71,10 +71,16 @@ async function handleQuery(msg: QueryMessage) {
}
}
if (!claudePath) {
send({ type: "agent_error", sessionId, message: "Claude CLI not found. Install Claude Code first." });
return;
}
try {
const q = query({
prompt,
options: {
pathToClaudeCodeExecutable: claudePath,
abortController: controller,
cwd: cwd || Deno.cwd(),
env: cleanEnv,
@ -144,6 +150,32 @@ function handleStop(msg: StopMessage) {
controller.abort();
}
function findClaudeCli(): string | undefined {
const home = Deno.env.get("HOME") ?? Deno.env.get("USERPROFILE") ?? "";
const candidates = [
`${home}/.local/bin/claude`,
`${home}/.claude/local/claude`,
"/usr/local/bin/claude",
"/usr/bin/claude",
];
for (const p of candidates) {
try { Deno.statSync(p); return p; } catch { /* not found */ }
}
try {
const proc = new Deno.Command("which", { args: ["claude"], stdout: "piped", stderr: "null" });
const out = new TextDecoder().decode(proc.outputSync().stdout).trim();
if (out) return out.split("\n")[0];
} catch { /* not found */ }
return undefined;
}
const claudePath = findClaudeCli();
if (claudePath) {
log(`Found Claude CLI at ${claudePath}`);
} else {
log("WARNING: Claude CLI not found — agent sessions will fail");
}
// Main: read NDJSON from stdin
log("Sidecar started (Deno)");
send({ type: "ready" });

View file

@ -4,6 +4,10 @@
import { stdin, stdout, stderr } from 'process';
import { createInterface } from 'readline';
import { execSync } from 'child_process';
import { existsSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import { query, type Query } from '@anthropic-ai/claude-agent-sdk';
const rl = createInterface({ input: stdin });
@ -81,9 +85,15 @@ async function handleQuery(msg: QueryMessage) {
}
try {
if (!claudePath) {
send({ type: 'agent_error', sessionId, message: 'Claude CLI not found. Install Claude Code first.' });
return;
}
const q = query({
prompt,
options: {
pathToClaudeCodeExecutable: claudePath,
abortController: controller,
cwd: cwd || process.cwd(),
env: cleanEnv,
@ -155,5 +165,31 @@ function handleStop(msg: StopMessage) {
session.controller.abort();
}
function findClaudeCli(): string | undefined {
// Check common locations
const candidates = [
join(homedir(), '.local', 'bin', 'claude'),
join(homedir(), '.claude', 'local', 'claude'),
'/usr/local/bin/claude',
'/usr/bin/claude',
];
for (const p of candidates) {
if (existsSync(p)) return p;
}
// Fall back to which/where
try {
return execSync('which claude 2>/dev/null || where claude 2>nul', { encoding: 'utf-8' }).trim().split('\n')[0];
} catch {
return undefined;
}
}
const claudePath = findClaudeCli();
if (claudePath) {
log(`Found Claude CLI at ${claudePath}`);
} else {
log('WARNING: Claude CLI not found — agent sessions will fail');
}
log('Sidecar started');
send({ type: 'ready' });