fix(launch-prompt): judge a Windows line per shell from measured damage

Stack QA on Windows found Claude/Codex given a launch-file pointer where main's
typed line delivered the exact text: Git Bash multi-line and 8,191-character
lines, PowerShell 5-line, 20 KB and 8,191-character lines, %NAME% pairs, and on
pwsh 7 `"` and a trailing `\`. The rule treated every Windows shell like cmd.

windowsLaunchLineVerdict now judges the typed line per shell from the measured
rows: cmd (8,191-character cap, no line break), PowerShell (CreateProcess's cap;
multi-line within the typed budget; 9 KB measured damaged), Git Bash (9,089
characters measured whole, 20,551 never started). A spelled-out .cmd/.bat shim
adds its cmd hop's damage. Its verdict is exact (ride the line), damaged (main's
paste on that path, else a launch file) or uncertain (what main does on that
path: its paste, or the line it typed), so a launch file appears only where
main's line was measured to fail. PowerShell `"` and a trailing `\` are
uncertain, because 5.1 breaks them and 7 does not.
This commit is contained in:
Brennan Benson
2026-10-02 06:06:03 -07:00
parent 6bdfb960da
commit c729128c3a
9 changed files with 336 additions and 74 deletions
@@ -135,10 +135,15 @@ describe('buildWorktreeStartupForAgent prompt carry', () => {
expect(result.startup.command).not.toContain('xxxx')
})
it('points a multi-line prompt at a launch file on a Windows host, which cannot stage', () => {
const result = build('summarize the diff\nthen list the risks', 'win32')
expect(result.startup.launchFile?.content).toBe('summarize the diff\nthen list the risks')
expect(result.startup.command).not.toContain('summarize')
// Why: measured on PowerShell, a short multi-line line arrives and a 9 KB one does not.
it('keeps a short multi-line prompt on a Windows line and points a long one at a file', () => {
expect(build('summarize the diff\nthen list the risks', 'win32').startup.command).toContain(
'summarize the diff\nthen list the risks'
)
const long = Array.from({ length: 20 }, (_, i) => `step ${i} `.padEnd(500, 'x')).join('\n')
const result = build(long, 'win32')
expect(result.startup.launchFile?.content).toBe(long)
expect(result.startup.command).not.toContain('step 1 ')
})
it('leaves a prompt needing a file the agent is not known to read as the follow-up paste', () => {
@@ -229,7 +229,7 @@ describe('agent launch caller prompt transport', () => {
['past the argv ceiling', 'gemini', 'x'.repeat(MAX_LINE_PROMPT_BYTES + 1), 'darwin'],
['a Windows-damaged prompt', 'gemini', 'say "hi"', 'win32'],
['past the argv ceiling', 'claude', 'x'.repeat(MAX_LINE_PROMPT_BYTES + 1), 'darwin'],
['a multi-line Windows prompt', 'codex', 'Fix the checks.\nThen push.', 'win32']
['a 9 KB multi-line Windows prompt', 'codex', `Fix the checks.\n${'x'.repeat(9_100)}`, 'win32']
] as const)(
'pastes %s for %s, and waits for that paste',
async (_label, agent, prompt, launchPlatform) => {
@@ -181,9 +181,10 @@ describe('launchAgentInNewTab Windows shell quoting', () => {
expect(mockCreateWebRuntimeAgentSessionTerminalWithLaunchDraft).not.toHaveBeenCalled()
})
it('pastes a multi-line AI-button prompt on Windows, as main did, instead of a launch file', async () => {
// Why: a 9 KB multi-line PowerShell line was measured losing its line breaks; main pasted it.
it('pastes a long multi-line AI-button prompt on Windows, as main did, instead of a launch file', async () => {
const { launchAgentInNewTab } = await import('./launch-agent-in-new-tab')
const prompt = 'Fix the failing checks.\nThen push.'
const prompt = `Fix the failing checks.\n${'Then push. '.repeat(900)}Done.`
launchAgentInNewTab({
agent: 'codex',
+19 -28
View File
@@ -6,7 +6,10 @@ import { RUNTIME_CAPABILITIES } from './protocol-version'
import { AGENT_LAUNCH_PROMPT_CARRY_RUNTIME_CAPABILITY } from './agent-launch-runtime-capability'
import { TYPED_STARTUP_LINE_BUDGET_BYTES, typedStartupLineFits } from './typed-startup-line'
import { describeLaunchHost } from './launch-host'
import { WINDOWS_TYPED_LINE_MAX_CHARS } from './launch-prompt-carry'
import {
WINDOWS_CMD_LINE_MAX_CHARS,
WINDOWS_POWERSHELL_LINE_MAX_CHARS
} from './windows-launch-line'
function plan(
agent: TuiAgent,
@@ -105,33 +108,21 @@ describe('where a launch prompt rides', () => {
})
describe('a host that types the line raw', () => {
// Why: the user's own words stay in the agent's history wherever a Windows line carries them.
it.each<['cmd' | 'powershell']>([['cmd'], ['powershell']])(
'keeps an exact %s line up to cmd’s cap and points Claude at a file past it',
(shell) => {
expect(plan('claude', 'fix it', { platform: 'win32', shell })?.carry).toBe('on-line')
expect(plan('claude', 'y'.repeat(4_000), { platform: 'win32', shell })?.carry).toBe('on-line')
const past = plan('claude', 'y'.repeat(WINDOWS_TYPED_LINE_MAX_CHARS), {
platform: 'win32',
shell
})
expect(past?.carry).toBe('launch-file')
}
)
// Why: the user's own words stay in the agent's history wherever a Windows line carries them;
// the per-shell measurements are in windows-launch-line.test.ts.
it('keeps an exact cmd line up to cmd’s cap and points Claude at a file past it', () => {
const extra = { platform: 'win32' as const, shell: 'cmd' as const }
expect(plan('claude', 'y'.repeat(4_000), extra)?.carry).toBe('on-line')
expect(plan('claude', 'y'.repeat(WINDOWS_CMD_LINE_MAX_CHARS), extra)?.carry).toBe('launch-file')
})
// The measured matrix (#23962 W-1): PowerShell splits at `"`, turns a trailing `\` into `"`, and
// a .cmd shim expands `%NAME%`; everything else arrives byte for byte.
it.each([
['a double quote', 'say "hi"', 'launch-file'],
['a trailing backslash', 'see C:\\dir\\', 'launch-file'],
['a %NAME% pair', 'echo %PATH%', 'launch-file'],
['a lone percent and an apostrophe', "it's 100% done", 'on-line']
] as const)(
'carries a PowerShell prompt with %s by the measured matrix',
(_label, text, carry) => {
expect(plan('claude', text, { platform: 'win32', shell: 'powershell' })?.carry).toBe(carry)
}
)
it('keeps a PowerShell line to CreateProcess’s cap and points Claude at a file past it', () => {
const extra = { platform: 'win32' as const, shell: 'powershell' as const }
expect(plan('claude', 'y'.repeat(20_000), extra)?.carry).toBe('on-line')
expect(plan('claude', 'y'.repeat(WINDOWS_POWERSHELL_LINE_MAX_CHARS), extra)?.carry).toBe(
'launch-file'
)
})
it('points Codex at a file for a Windows-damaged prompt, and keeps it on Gemini’s line', () => {
const prompt = 'fix the build\nthen run the tests'
@@ -213,7 +204,7 @@ describe('a host that types the line raw', () => {
paired: false
})
const prompt = 'fix the build\nthen run the tests'
const extra = { platform: 'win32' as const, shell: 'powershell' as const, host }
const extra = { platform: 'win32' as const, shell: 'cmd' as const, host }
expect(plan('claude', prompt, extra)?.carry).toBe('on-line')
expect(plan('codex', 'y'.repeat(MAX_LINE_PROMPT_BYTES + 1), extra)?.carry).toBe('on-line')
expect(plan('claude', prompt, { ...extra, paste: 'once-agent-runs' })?.carry).toBe(
+16 -24
View File
@@ -15,6 +15,7 @@ import {
import { hasControlByte, typedStartupLineFits } from './typed-startup-line'
import type { TuiAgent } from './tui-agent'
import type { LaunchHost } from './launch-host'
import { windowsLaunchLineVerdict } from './windows-launch-line'
/**
* Whether a Windows shell would damage `prompt` as one quoted argument. No quoting keeps a control
@@ -98,28 +99,13 @@ export function agentReadsLaunchFile(agent: TuiAgent): boolean {
const utf8 = new TextEncoder()
/** cmd.exe's documented line cap, the smallest of the Windows shells Orca types into. */
export const WINDOWS_TYPED_LINE_MAX_CHARS = 8191
/**
* Whether a Windows shell carries `prompt` exactly on `line`. Nothing stages a Windows line, so it
* must hold no control byte, fit cmd's cap, and survive the shell's quoting (measured matrix:
* `windowsShellDamagesPrompt`).
*/
function windowsLineCarriesExactly(prompt: string, line: string, shell: AgentStartupShell) {
return (
!hasControlByte(line) &&
line.length <= WINDOWS_TYPED_LINE_MAX_CHARS &&
!windowsShellDamagesPrompt(prompt, shell)
)
}
/**
* The one carry rule. The prompt rides the agent's line: a host that stages (POSIX, SSH, WSL)
* stages it when it is long or multi-line, and a Windows host types it when its shell carries the
* text exactly. When the line cannot (past the argv ceiling, damaged by a Windows shell, past a
* paired host's typed budget), a caller whose paste main used gets that paste, so the agent receives
* the user's text. Otherwise it rides a launch file, which goes only to an agent measured reading
* stages it when it is long or multi-line, and a Windows host types it when its shell was measured
* carrying such a line exactly (`windowsLaunchLineVerdict`); an unmeasured one does what main does
* on the caller's path. When the line cannot (past the argv ceiling, measured damaged by a Windows
* shell, past a paired host's typed budget), a caller whose paste main used gets that paste, so the
* agent receives the user's text. Otherwise it rides a launch file, which goes only to an agent measured reading
* one, on a host that writes it. Failing a file it is pasted once the agent is ready, unless the
* caller's paste cannot reach the agent on this host: then the line carries it, as main typed it.
*/
@@ -183,12 +169,18 @@ export function carryLaunchPrompt<A extends CarriedPlanArgs, P extends { launchC
if (readsEnv) {
return { carry: 'on-line', plan }
}
if (args.platform === 'win32' && !windowsLineCarriesExactly(text, plan.launchCommand, shell)) {
const windowsLine =
args.platform === 'win32' ? windowsLaunchLineVerdict(text, plan.launchCommand, shell) : 'exact'
// Why #24257's typed budget: an older paired host may type the line raw, truncated past it.
if (
windowsLine === 'damaged' ||
(args.host.paired && !typedStartupLineFits(plan.launchCommand))
) {
return viaLaunchFile()
}
// Why #24257's typed budget: an older paired host may type the line raw, truncated past it.
if (args.host.paired && !typedStartupLineFits(plan.launchCommand)) {
return viaLaunchFile()
// Why: unmeasured, so this path does what main does: its paste, or the line it typed.
if (windowsLine === 'uncertain' && args.paste === 'once-agent-runs') {
return pasteAfterReady()
}
return { carry: 'on-line', plan }
}
+4 -3
View File
@@ -137,7 +137,8 @@ describe('tui agent startup plans', () => {
})
// Why: a Windows host stages nothing, so Git Bash would get the raw line break typed.
it('points Git Bash on a Windows host at a launch file for a multi-line prompt', () => {
// Why: measured, a multi-line line arrives whole in Git Bash, as main typed it.
it('keeps a multi-line prompt on a Git Bash line on a Windows host', () => {
const plan = planLaunchForTest({
agent: 'claude',
prompt: 'line one\nline two',
@@ -146,8 +147,8 @@ describe('tui agent startup plans', () => {
shell: 'posix'
})
expect(plan?.launchFile?.content).toBe('line one\nline two')
expect(plan?.launchCommand).not.toContain('line one')
expect(plan?.launchFile).toBeUndefined()
expect(plan?.launchCommand).toContain('line one\nline two')
})
it('leaves a multi-line draft for the paste on a Windows shell instead of typing it', () => {
+164
View File
@@ -0,0 +1,164 @@
import { describe, expect, it } from 'vitest'
import { describeLaunchHost } from './launch-host'
import { planLaunchPrompt } from './tui-agent-startup'
import type { AgentStartupShell } from './tui-agent-startup-shell'
import type { TuiAgent } from './tui-agent'
// The stack QA's prompts (Windows lane, 2026-10-02), built the way its rig built them.
function qaLine(tag: string, length: number): string {
let text = `QA-STACK ${tag} start.`
for (let i = 1; text.length < length - 14; i += 1) {
text += ` w${i}`
}
text += ` ${tag} end.`
return text.padEnd(length, '.').slice(0, length)
}
const PROMPTS = {
p2k: qaLine('P2K', 2048),
p20k: qaLine('P20K', 20480),
p100k: qaLine('P100K', 99000),
ml5: [1, 2, 3, 4, 5]
.map((i) => `QA-STACK ML5 line ${i}: inert text, do not run any command.`)
.join('\n'),
ml9k: Array.from({ length: 18 }, (_, i) => qaLine(`ML9K-L${i + 1}`, 500)).join('\n'),
e8100: qaLine('E8100', 8100),
e8191: qaLine('E8191', 8191),
pq: 'QA-STACK PQ one line with a "double quote" inside, inert, do not run any command.',
ppct: 'QA-STACK PPCT one line with a %QAVAR% pair inside, inert, do not run any command.',
pbs: 'QA-STACK PBS one line ending in a backslash, inert, do not run any command \\'
} as const
type Row = keyof typeof PROMPTS
function carry(
agent: TuiAgent,
row: Row,
shell: AgentStartupShell,
command = `node C:/Users/neil/orca-qa/stack-final/win/bin/stub.js --qa-as=${agent}`
) {
return planLaunchPrompt({
agent,
prompt: PROMPTS[row],
cmdOverrides: { [agent]: command },
platform: 'win32',
shell,
host: describeLaunchHost({
launchPlatform: 'win32',
isRemote: false,
hostPlatform: 'win32',
paired: false
}),
paste: 'when-host-proves-agent'
})?.carry
}
// Each row: what main's typed line did, then the carry it gets. A launch file only where main's
// line was measured to damage or lose the prompt; everywhere else the line, as main typed it.
describe('an agent.launch prompt on a Windows host, per measured shell', () => {
it.each<[AgentStartupShell, Row, string, 'on-line' | 'launch-file']>([
['cmd', 'p2k', 'exact', 'on-line'],
['cmd', 'p20k', 'input line too long', 'launch-file'],
['cmd', 'ml5', 'damaged', 'launch-file'],
['cmd', 'ml9k', 'input line too long', 'launch-file'],
['cmd', 'e8100', 'exact', 'on-line'],
['cmd', 'e8191', 'input line too long', 'launch-file'],
['cmd', 'pq', 'damaged by main’s quoting, exact on this line', 'on-line'],
['cmd', 'ppct', 'damaged by main’s quoting, exact on this line', 'on-line'],
['cmd', 'pbs', 'damaged by main’s quoting, exact on this line', 'on-line'],
['powershell', 'p2k', 'exact on 5.1 and 7', 'on-line'],
['powershell', 'p20k', 'exact on 5.1 and 7', 'on-line'],
['powershell', 'p100k', 'never started on 5.1 and 7', 'launch-file'],
['powershell', 'ml5', 'exact on 5.1 and 7', 'on-line'],
['powershell', 'ml9k', 'damaged on 5.1, lines run as commands on 7', 'launch-file'],
['powershell', 'e8191', 'exact on 5.1 and 7', 'on-line'],
['powershell', 'pq', 'exact on 7, damaged on 5.1 (version unknown here)', 'on-line'],
['powershell', 'ppct', 'exact on 5.1 and 7', 'on-line'],
['powershell', 'pbs', 'exact on 7, damaged on 5.1 (version unknown here)', 'on-line'],
['posix', 'p2k', 'exact in Git Bash', 'on-line'],
['posix', 'p20k', 'never started in Git Bash', 'launch-file'],
['posix', 'ml5', 'exact in Git Bash', 'on-line'],
['posix', 'ml9k', 'exact in Git Bash', 'on-line'],
['posix', 'e8191', 'exact in Git Bash', 'on-line'],
['posix', 'pq', 'exact in Git Bash', 'on-line'],
['posix', 'ppct', 'exact in Git Bash', 'on-line'],
['posix', 'pbs', 'exact in Git Bash', 'on-line']
])('%s %s (main: %s) rides %s', (shell, row, _main, expected) => {
expect(carry('claude', row, shell)).toBe(expected)
expect(carry('codex', row, shell)).toBe(expected)
})
// Why: a shim's cmd.exe re-reads the line, so cmd's cap, its `%NAME%` expansion and a cut at a
// line break apply, and PowerShell hands a shim `"` and a trailing `\` the legacy way.
it.each<[AgentStartupShell, Row, 'on-line' | 'launch-file']>([
['powershell', 'p2k', 'on-line'],
['powershell', 'pq', 'launch-file'],
['powershell', 'ppct', 'launch-file'],
['powershell', 'pbs', 'launch-file'],
['posix', 'ppct', 'launch-file'],
['posix', 'pq', 'on-line'],
// Unmeasured through a shim (its cmd.exe's cap and line-break cut), so typed as main typed it.
['powershell', 'ml5', 'on-line'],
['powershell', 'e8191', 'on-line'],
['posix', 'ml5', 'on-line']
])('through a spelled-out .cmd shim, %s %s rides %s', (shell, row, expected) => {
expect(carry('claude', row, shell, 'C:/Users/ada/AppData/Roaming/npm/claude.cmd')).toBe(
expected
)
})
// Why: a typed carriage return is Enter (in cmd the rest runs as commands) and a Tab completes.
it.each<AgentStartupShell>(['cmd', 'powershell', 'posix'])(
'never types a Tab or a carriage return into %s',
(shell) => {
const planned = (paste: 'once-agent-runs' | 'when-host-proves-agent') =>
planLaunchPrompt({
agent: 'claude',
prompt: 'before\tmiddle\r& echo PWNED',
cmdOverrides: {},
platform: 'win32',
shell,
host: describeLaunchHost({
launchPlatform: 'win32',
isRemote: false,
hostPlatform: 'win32',
paired: false
}),
paste
})?.carry
expect(planned('once-agent-runs')).toBe('paste-after-ready')
expect(planned('when-host-proves-agent')).toBe('launch-file')
}
)
// Why: an AI button's prompt was pasted on main, so only a line measured exact replaces that
// paste; measured damage and the unmeasured both keep main's paste.
it.each<[AgentStartupShell, Row, 'on-line' | 'paste-after-ready']>([
['cmd', 'ml5', 'paste-after-ready'],
['cmd', 'pq', 'on-line'],
['powershell', 'ml5', 'on-line'],
['powershell', 'ml9k', 'paste-after-ready'],
['powershell', 'pq', 'paste-after-ready'],
['powershell', 'p20k', 'on-line'],
['posix', 'ml9k', 'on-line'],
['posix', 'p20k', 'paste-after-ready']
])('an AI button on %s with %s gets %s', (shell, row, expected) => {
const planned = planLaunchPrompt({
agent: 'claude',
prompt: PROMPTS[row],
cmdOverrides: {
claude: 'node C:/Users/neil/orca-qa/stack-final/win/bin/stub.js --qa-as=claude'
},
platform: 'win32',
shell,
host: describeLaunchHost({
launchPlatform: 'win32',
isRemote: false,
hostPlatform: 'win32',
paired: false
}),
paste: 'once-agent-runs'
})
expect(planned?.carry).toBe(expected)
})
})
+96
View File
@@ -0,0 +1,96 @@
/**
* What a typed Windows launch line does to its prompt. Nothing stages a Windows line, so this is per
* shell, from lines measured through each one (stack QA, native agent executables, 2026-10-02):
* `exact` arrived byte for byte, `damaged` was measured lost or changed, and `uncertain` was not
* measured, so the caller does there what main does on its path.
*/
import type { AgentStartupShell } from './tui-agent-startup-shell'
import { TYPED_STARTUP_LINE_BUDGET_BYTES } from './typed-startup-line'
export type WindowsLineVerdict = 'exact' | 'damaged' | 'uncertain'
/** cmd.exe's line cap: an 8,172-character line ran, an 8,263-character one did not start. */
export const WINDOWS_CMD_LINE_MAX_CHARS = 8191
/** CreateProcess's 32,767-character command line, less room for the resolved executable path.
* Measured: 20,552 characters arrived, 99,072 never started. */
export const WINDOWS_POWERSHELL_LINE_MAX_CHARS = 32_000
/** A multi-line PowerShell line: 356 bytes arrived; 9,088 lost its line breaks (5.1) or ran its
* lines as commands (7). Exact is held to the typed budget, damaged from the measured failure. */
const WINDOWS_POWERSHELL_MULTI_LINE_DAMAGED_BYTES = 9_088
/** Git Bash: 9,089 characters arrived whole, line breaks included (typed in 17 s); 20,551 never
* started. */
export const WINDOWS_POSIX_LINE_EXACT_MAX_CHARS = 9_089
const WINDOWS_POSIX_LINE_DAMAGED_CHARS = 20_551
const encoder = new TextEncoder()
/** A control byte other than a line feed: every Windows line editor reads it as a key (a carriage
* return is Enter, which in cmd runs the rest as commands; Tab completes), so the bytes change. */
function hasKeyByte(line: string): boolean {
for (let i = 0; i < line.length; i += 1) {
const code = line.charCodeAt(i)
if ((code < 0x20 && code !== 0x0a) || code === 0x7f) {
return true
}
}
return false
}
/** An agent spelled as a `.cmd`/`.bat` shim, whose cmd.exe re-reads its arguments. A bare name
* may be either, so only the spelled-out shim counts. */
function launchesThroughCmdShim(line: string): boolean {
const target = /^(?:&\s*)?(?:'([^']*)'|"([^"]*)"|(\S+))/.exec(line.trim())
return /\.(?:cmd|bat)$/i.test(target?.[1] ?? target?.[2] ?? target?.[3] ?? '')
}
function shellVerdict(line: string, shell: AgentStartupShell): WindowsLineVerdict {
const multiLine = line.includes('\n')
if (shell === 'cmd') {
// A line feed submits early; `"`, `%NAME%` and a trailing `\` arrive.
return multiLine || line.length > WINDOWS_CMD_LINE_MAX_CHARS ? 'damaged' : 'exact'
}
if (shell === 'powershell') {
if (line.length > WINDOWS_POWERSHELL_LINE_MAX_CHARS) {
return 'damaged'
}
const bytes = encoder.encode(line).byteLength
if (!multiLine || bytes <= TYPED_STARTUP_LINE_BUDGET_BYTES) {
return 'exact'
}
return bytes >= WINDOWS_POWERSHELL_MULTI_LINE_DAMAGED_BYTES ? 'damaged' : 'uncertain'
}
// Git Bash (and a WSL shell): POSIX quoting arrived whole.
if (line.length <= WINDOWS_POSIX_LINE_EXACT_MAX_CHARS) {
return 'exact'
}
return line.length >= WINDOWS_POSIX_LINE_DAMAGED_CHARS ? 'damaged' : 'uncertain'
}
export function windowsLaunchLineVerdict(
prompt: string,
line: string,
shell: AgentStartupShell
): WindowsLineVerdict {
if (hasKeyByte(line)) {
return 'damaged'
}
const verdict = shellVerdict(line, shell)
const legacyArgsDamage = prompt.includes('"') || prompt.endsWith('\\')
if (shell === 'cmd' || !launchesThroughCmdShim(line)) {
// Why: 5.1 hands every native command `"` and a trailing `\` the legacy way, 7 only a shim;
// this client cannot tell which PowerShell runs.
return shell === 'powershell' && verdict === 'exact' && legacyArgsDamage ? 'uncertain' : verdict
}
// Measured (#23962 W-1): PowerShell hands a shim `"` and a trailing `\` the legacy way, and the
// shim's cmd.exe expands `%NAME%`.
if (/%[^%\s]+%/.test(prompt) || (shell === 'powershell' && legacyArgsDamage)) {
return 'damaged'
}
// The shim's cmd.exe also caps the line and cuts at a line break; not measured through a shim.
return verdict === 'exact' && (line.includes('\n') || line.length > WINDOWS_CMD_LINE_MAX_CHARS)
? 'uncertain'
: verdict
}
+24 -12
View File
@@ -5,11 +5,13 @@ import { buildAgentDraftLaunchPlan } from './tui-agent-startup'
import type { AgentStartupShell } from './tui-agent-startup-shell'
import { windowsDraftRefusal } from './launch-prompt-carry'
function plan(prompt: string, shell: AgentStartupShell) {
const SHIM = 'C:/Users/ada/AppData/Roaming/npm/claude.cmd'
function plan(prompt: string, shell: AgentStartupShell, command?: string) {
return planLaunchForTest({
agent: 'claude',
prompt,
cmdOverrides: {},
cmdOverrides: command ? { claude: command } : {},
platform: shell === 'posix' ? 'darwin' : 'win32',
shell
})
@@ -18,7 +20,9 @@ function plan(prompt: string, shell: AgentStartupShell) {
describe('a prompt a Windows shell would damage on the launch line', () => {
// Measured (ps-quoting-matrix.md): PowerShell 5.1, and 7.x through a .cmd shim, split a `"` out of
// the plain literal and turn a trailing backslash into `"`; the legacy-passing escape would let a
// .cmd shim's cmd.exe run `&` and `<>` from inside the user's quotes. So these ride a launch file.
// .cmd shim's cmd.exe run `&` and `<>` from inside the user's quotes. A spelled-out shim gets a
// launch file. A bare name may be a native executable, where 7 carried them exactly (stack QA),
// so its line is typed as main typed it.
it.each([
['P1', 'fix the "foo bar" bug'],
['P2', '"leading quote" then text'],
@@ -27,12 +31,16 @@ describe('a prompt a Windows shell would damage on the launch line', () => {
['P5', 'back\\slash\\\\ "q" end\\'],
['P7', 'replace "<b>" with "a & b"'],
['a bare path', 'see C:\\dir\\']
])('moves %s into a launch file on PowerShell', (_, prompt) => {
const startup = plan(prompt, 'powershell')
expect(startup?.launchFile?.content).toBe(prompt)
expect(startup?.launchCommand).not.toContain('"')
expect(startup?.launchCommand).not.toContain('Legacy')
})
])(
'moves %s into a launch file through a PowerShell shim, and types it for a bare name',
(_, prompt) => {
const startup = plan(prompt, 'powershell', SHIM)
expect(startup?.launchFile?.content).toBe(prompt)
expect(startup?.launchCommand).not.toContain('"')
expect(startup?.launchCommand).not.toContain('Legacy')
expect(plan(prompt, 'powershell')?.launchFile).toBeUndefined()
}
)
// Measured form F-A: a pointer to a spaced path passed in every PowerShell and target.
it('types P6, the backtick pointer, and quote-free prompts as a plain literal', () => {
@@ -41,9 +49,13 @@ describe('a prompt a Windows shell would damage on the launch line', () => {
expect(plan("fix Bob's build", 'powershell')?.launchCommand).toBe("claude 'fix Bob''s build'")
})
// Measured (QA-WIN R0-R2): through a `.cmd` shim, `%PATH%` reached the agent as 1,795 characters.
it('moves a %NAME% pair into a launch file on PowerShell and keeps a lone percent typed', () => {
expect(plan('echo %PATH% for me', 'powershell')?.launchFile?.content).toBe('echo %PATH% for me')
// Measured (QA-WIN R0-R2): through a `.cmd` shim, `%PATH%` reached the agent as 1,795 characters;
// to a native executable it arrived (stack QA).
it('moves a %NAME% pair into a launch file through a shim and keeps a lone percent typed', () => {
expect(plan('echo %PATH% for me', 'powershell', SHIM)?.launchFile?.content).toBe(
'echo %PATH% for me'
)
expect(plan('echo %PATH% for me', 'powershell')?.launchFile).toBeUndefined()
expect(plan('coverage is 80% now', 'powershell')?.launchCommand).toBe(
"claude 'coverage is 80% now'"
)