From 2e6fc2af7d7119ebca71f8e890aabb4be26ebcd3 Mon Sep 17 00:00:00 2001 From: 7418 <7418@openclaw.ai> Date: Fri, 6 Feb 2026 22:23:18 +0800 Subject: [PATCH] fix: pass claude binary path to Agent SDK for packaged app The SDK's query() function also needs to find the claude binary. Added findClaudePath() in claude-client.ts and pass it via pathToClaudeCodeExecutable option, fixing "Failed to create session" in packaged builds where PATH is limited. Co-Authored-By: Claude Opus 4.6 --- src/lib/claude-client.ts | 53 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/lib/claude-client.ts b/src/lib/claude-client.ts index 645b4d6..5b49268 100644 --- a/src/lib/claude-client.ts +++ b/src/lib/claude-client.ts @@ -14,6 +14,53 @@ import type { } from '@anthropic-ai/claude-agent-sdk'; import type { ClaudeStreamOptions, SSEEvent, TokenUsage, MCPServerConfig, PermissionRequestEvent } from '@/types'; import { registerPendingPermission } from './permission-registry'; +import { execFileSync } from 'child_process'; +import os from 'os'; +import path from 'path'; + +let cachedClaudePath: string | null | undefined; + +function findClaudePath(): string | undefined { + if (cachedClaudePath !== undefined) return cachedClaudePath || undefined; + + const home = os.homedir(); + const candidates = [ + '/usr/local/bin/claude', + '/opt/homebrew/bin/claude', + path.join(home, '.npm-global', 'bin', 'claude'), + path.join(home, '.local', 'bin', 'claude'), + path.join(home, '.claude', 'bin', 'claude'), + ]; + + for (const p of candidates) { + try { + execFileSync(p, ['--version'], { timeout: 3000, stdio: 'pipe' }); + cachedClaudePath = p; + return p; + } catch { + // not found + } + } + + // Fallback: which claude + const extra = ['/usr/local/bin', '/opt/homebrew/bin', '/usr/bin', '/bin', + path.join(home, '.npm-global', 'bin'), path.join(home, '.nvm', 'current', 'bin'), + path.join(home, '.local', 'bin'), path.join(home, '.claude', 'bin')]; + const current = (process.env.PATH || '').split(':'); + for (const p of extra) { if (!current.includes(p)) current.push(p); } + + try { + const result = execFileSync('/usr/bin/which', ['claude'], { + timeout: 3000, stdio: 'pipe', + env: { ...process.env, PATH: current.join(':') }, + }); + const found = result.toString().trim(); + if (found) { cachedClaudePath = found; return found; } + } catch { /* not found */ } + + cachedClaudePath = null; + return undefined; +} /** * Convert our MCPServerConfig to the SDK's McpStdioServerConfig format @@ -92,6 +139,12 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream