From 3f465cd47d40335d53c8b790b4efdf73496c4cfd Mon Sep 17 00:00:00 2001 From: weishu Date: Sat, 17 Jan 2026 19:59:42 +0800 Subject: [PATCH] fix: escape newlines in shell arguments on Windows close #70 --- cli/src/claude/claudeLocal.ts | 3 ++- cli/src/claude/sdk/query.ts | 7 ++++--- cli/src/utils/shellEscape.ts | 10 ++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 cli/src/utils/shellEscape.ts diff --git a/cli/src/claude/claudeLocal.ts b/cli/src/claude/claudeLocal.ts index b99964d4..bce6904e 100644 --- a/cli/src/claude/claudeLocal.ts +++ b/cli/src/claude/claudeLocal.ts @@ -8,6 +8,7 @@ import { systemPrompt } from "./utils/systemPrompt"; import { withBunRuntimeEnv } from "@/utils/bunRuntime"; import { spawnWithAbort } from "@/utils/spawnWithAbort"; import { getHapiBlobsDir } from "@/constants/uploadPaths"; +import { stripNewlinesForWindowsShellArg } from "@/utils/shellEscape"; export async function claudeLocal(opts: { abort: AbortSignal, @@ -50,7 +51,7 @@ export async function claudeLocal(opts: { args.push('--resume', startFrom); } - args.push('--append-system-prompt', systemPrompt); + args.push('--append-system-prompt', stripNewlinesForWindowsShellArg(systemPrompt)); const cleanupMcpConfig = appendMcpConfigArg(args, opts.mcpServers, { baseDir: projectDir diff --git a/cli/src/claude/sdk/query.ts b/cli/src/claude/sdk/query.ts index 8cf3bf88..bf443860 100644 --- a/cli/src/claude/sdk/query.ts +++ b/cli/src/claude/sdk/query.ts @@ -25,6 +25,7 @@ import { import { getDefaultClaudeCodePath, logDebug, streamToStdin } from './utils' import { withBunRuntimeEnv } from '@/utils/bunRuntime' import { killProcessByChildProcess } from '@/utils/process' +import { stripNewlinesForWindowsShellArg } from '@/utils/shellEscape' import type { Writable } from 'node:stream' import { logger } from '@/ui/logger' import { appendMcpConfigArg } from '../utils/mcpConfig' @@ -289,8 +290,8 @@ export function query(config: { const args = ['--output-format', 'stream-json', '--verbose'] let cleanupMcpConfig: (() => void) | null = null - if (customSystemPrompt) args.push('--system-prompt', customSystemPrompt) - if (appendSystemPrompt) args.push('--append-system-prompt', appendSystemPrompt) + if (customSystemPrompt) args.push('--system-prompt', stripNewlinesForWindowsShellArg(customSystemPrompt)) + if (appendSystemPrompt) args.push('--append-system-prompt', stripNewlinesForWindowsShellArg(appendSystemPrompt)) if (maxTurns) args.push('--max-turns', maxTurns.toString()) if (model) args.push('--model', model) if (canCallTool) { @@ -317,7 +318,7 @@ export function query(config: { // Handle prompt input if (typeof prompt === 'string') { - args.push('--print', prompt.trim()) + args.push('--print', stripNewlinesForWindowsShellArg(prompt.trim())) } else { args.push('--input-format', 'stream-json') } diff --git a/cli/src/utils/shellEscape.ts b/cli/src/utils/shellEscape.ts new file mode 100644 index 00000000..8d34e037 --- /dev/null +++ b/cli/src/utils/shellEscape.ts @@ -0,0 +1,10 @@ +/** + * Strip newlines when passing args through Windows cmd.exe. + * cmd.exe treats CR/LF as command separators and truncates multiline args. + */ +export function stripNewlinesForWindowsShellArg(value: string): string { + if (process.platform !== 'win32') { + return value; + } + return value.replace(/\r?\n/g, ' '); +}