Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cli/src/agent/backends/acp/AcpStdioTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export class AcpStdioTransport {
this.process = spawn(options.command, options.args ?? [], {
env: options.env,
stdio: ['pipe', 'pipe', 'pipe'],
shell: process.platform === 'win32'
shell: process.platform === 'win32',
windowsHide: true
});

this.process.stdout.setEncoding('utf8');
Expand Down
14 changes: 7 additions & 7 deletions cli/src/claude/claudeLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { withBunRuntimeEnv } from "@/utils/bunRuntime";
import { spawnWithAbort } from "@/utils/spawnWithAbort";
import { getHapiBlobsDir } from "@/constants/uploadPaths";
import { stripNewlinesForWindowsShellArg } from "@/utils/shellEscape";
import { getDefaultClaudeCodePath } from "./sdk/utils";
import { getClaudeCodeExecutable } from "./sdk/utils";

export async function claudeLocal(opts: {
abort: AbortSignal,
Expand Down Expand Up @@ -85,16 +85,16 @@ export async function claudeLocal(opts: {

logger.debug(`[ClaudeLocal] Spawning claude with args: ${JSON.stringify(args)}`);

// Get Claude executable path (absolute path on Windows for shell: false)
const claudeCommand = getDefaultClaudeCodePath();
logger.debug(`[ClaudeLocal] Using claude executable: ${claudeCommand}`);
// Get Claude executable info (resolves .cmd to node + entry script on Windows)
const claude = getClaudeCodeExecutable();
logger.debug(`[ClaudeLocal] Using claude executable: ${claude.command} ${claude.prependArgs.join(' ')}`);

// Spawn the process
try {
process.stdin.pause();
await spawnWithAbort({
command: claudeCommand,
args,
command: claude.command,
args: [...claude.prependArgs, ...args],
cwd: opts.path,
env: withBunRuntimeEnv(env, { allowBunBeBun: false }),
signal: opts.abort,
Expand All @@ -103,7 +103,7 @@ export async function claudeLocal(opts: {
installHint: 'Claude CLI',
includeCause: true,
logExit: true,
shell: false // Use absolute path, no shell needed
shell: false
});
} finally {
cleanupMcpConfig?.();
Expand Down
2 changes: 0 additions & 2 deletions cli/src/claude/claudeRemote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { awaitFileExist } from "@/modules/watcher/awaitFileExist";
import { systemPrompt } from "./utils/systemPrompt";
import { PermissionResult } from "./sdk/types";
import { getHapiBlobsDir } from "@/constants/uploadPaths";
import { getDefaultClaudeCodePath } from "./sdk/utils";

export async function claudeRemote(opts: {

Expand Down Expand Up @@ -123,7 +122,6 @@ export async function claudeRemote(opts: {
disallowedTools: initial.mode.disallowedTools,
canCallTool: (toolName: string, input: unknown, options: { signal: AbortSignal }) => opts.canCallTool(toolName, input, mode, options),
abort: opts.signal,
pathToClaudeCodeExecutable: getDefaultClaudeCodePath(),
settingsPath: opts.hookSettingsPath,
additionalDirectories: [getHapiBlobsDir()],
}
Expand Down
35 changes: 18 additions & 17 deletions cli/src/claude/sdk/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
type PermissionResult,
AbortError
} from './types'
import { getDefaultClaudeCodePath, logDebug, streamToStdin } from './utils'
import { getClaudeCodeExecutable, resolveClaudeExecutable, logDebug, streamToStdin } from './utils'
import { withBunRuntimeEnv } from '@/utils/bunRuntime'
import { killProcessByChildProcess } from '@/utils/process'
import { stripNewlinesForWindowsShellArg } from '@/utils/shellEscape'
Expand Down Expand Up @@ -269,7 +269,7 @@ export function query(config: {
disallowedTools = [],
maxTurns,
mcpServers,
pathToClaudeCodeExecutable = getDefaultClaudeCodePath(),
pathToClaudeCodeExecutable,
permissionMode = 'default',
continue: continueConversation,
resume,
Expand Down Expand Up @@ -323,33 +323,34 @@ export function query(config: {
args.push('--input-format', 'stream-json')
}

// Determine how to spawn Claude Code
// - If it's just 'claude' command → spawn('claude', args) with shell on Windows
// - If it's a full path to binary or script → spawn(path, args)
const isCommandOnly = pathToClaudeCodeExecutable === 'claude'

// Validate executable path (skip for command-only mode)
if (!isCommandOnly && !existsSync(pathToClaudeCodeExecutable)) {
throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`)
// Resolve the Claude executable
// If user provided a path, resolve it (handles .cmd → node + entry script)
// Otherwise use getClaudeCodeExecutable() which searches known locations
const claude = pathToClaudeCodeExecutable
? resolveClaudeExecutable(pathToClaudeCodeExecutable)
: getClaudeCodeExecutable()

// Validate executable path (skip for command-only like 'claude')
const isCommandOnly = claude.command === 'claude'
if (!isCommandOnly && !existsSync(claude.command)) {
throw new ReferenceError(`Claude Code executable not found at ${claude.command}. Is options.pathToClaudeCodeExecutable set?`)
}

const spawnCommand = pathToClaudeCodeExecutable
const spawnArgs = args
const spawnArgs = [...claude.prependArgs, ...args]

cleanupMcpConfig = appendMcpConfigArg(spawnArgs, mcpServers)

// Spawn Claude Code process
const spawnEnv = withBunRuntimeEnv(process.env, { allowBunBeBun: false })
logDebug(`Spawning Claude Code process: ${spawnCommand} ${spawnArgs.join(' ')}`)
logDebug(`Spawning Claude Code process: ${claude.command} ${spawnArgs.join(' ')}`)

const child = spawn(spawnCommand, spawnArgs, {
const child = spawn(claude.command, spawnArgs, {
cwd,
stdio: ['pipe', 'pipe', 'pipe'],
signal: config.options?.abort,
env: spawnEnv,
// Use shell: false with absolute path from getDefaultClaudeCodePath()
// This avoids cmd.exe resolution issues on Windows
shell: false
shell: false,
windowsHide: true
}) as ChildProcessWithoutNullStreams

// Handle stdin
Expand Down
Loading