@@ -149,12 +149,27 @@ function buildMacSandboxProfile(workspaceRoot: string, allowNetwork: boolean): s
149149 ] . join ( "\n" ) ;
150150}
151151
152- // POSIX shell single-quoting: wraps in '...', escaping any embedded single
153- // quote as '\''. Used to fold a wrapped {command, args} back into the single
154- // shell-command string that `child_process.exec`/`spawn(..., {shell:true})`
155- // expect, without needing to change how the rest of agent-tools.ts invokes
156- // commands.
157- function shellQuote ( arg : string ) : string {
152+ // Quotes a single argument so the shell that will run it treats it as one
153+ // literal value. Used both to fold a wrapped {command, args} back into the
154+ // single shell-command string that `child_process.exec`/`spawn(...,
155+ // {shell:true})` expect, and by agent-tools.ts when it builds a fixed command
156+ // around a *value* the model supplied (a path for `git diff`, a message for
157+ // `git commit`).
158+ //
159+ // The two shells need different treatment, and getting this wrong in either
160+ // direction is a bug:
161+ //
162+ // - POSIX `sh`: single quotes, with an embedded quote written as '\''.
163+ // Double quotes would not be enough, because `$(...)` and backticks are
164+ // still expanded inside them.
165+ // - Windows `cmd.exe`: single quotes are not quote characters at all, so
166+ // POSIX quoting there would corrupt ordinary arguments rather than protect
167+ // them. Double quotes are the right tool: `&`, `|`, `<` and `>` are
168+ // literal inside them, and `$(...)`/backticks mean nothing to cmd.exe.
169+ // An embedded double quote is written as "" — the convention both cmd.exe
170+ // and the argv parser of the program being launched understand.
171+ export function shellQuote ( arg : string , platform : NodeJS . Platform = process . platform ) : string {
172+ if ( platform === "win32" ) return `"${ arg . replace ( / " / g, '""' ) } "` ;
158173 return `'${ arg . replace ( / ' / g, `'\\''` ) } '` ;
159174}
160175
@@ -171,5 +186,5 @@ export function applySandbox(
171186) : string {
172187 const wrapped = wrapCommand ( command , opts , platform , hasCommand ) ;
173188 if ( ! wrapped ) return command ;
174- return [ wrapped . command , ...wrapped . args ] . map ( shellQuote ) . join ( " " ) ;
189+ return [ wrapped . command , ...wrapped . args ] . map ( ( arg ) => shellQuote ( arg , platform ) ) . join ( " " ) ;
175190}
0 commit comments