diff --git a/src/shared/startup-command-staging.test.ts b/src/shared/startup-command-staging.test.ts index 98397d0265f..8b8cce30903 100644 --- a/src/shared/startup-command-staging.test.ts +++ b/src/shared/startup-command-staging.test.ts @@ -146,6 +146,38 @@ describe('stageStartupCommand', () => { } ) + // Why: behind `/bin/sh` the agent is not the shell's own job, and a pasted prompt waits on that. + it.each([['/bin/tcsh'], ['/usr/bin/nu'], [undefined]])( + 'types a short plain Orca-built line as is for a shell that cannot source it (%s)', + (shellPath) => { + const command = `claude '--dangerously-skip-permissions' '--model' 'claude-opus-5-5[1m]'` + expect( + stageStartupCommand({ + command, + shellPath, + orcaBuiltLine: true, + platform: 'darwin', + directory + }) + ).toEqual({ command, delivery: 'typed' }) + expect(readdirSync(directory)).toEqual([]) + } + ) + + it.each([[`claude 'it'\\''s'`], ['claude "x"'], ["claude '$HOME'"], ["claude 'a`b'"]])( + 'stages a short Orca-built line tcsh would not read literally: %s', + (command) => { + const staged = stageStartupCommand({ + command, + shellPath: '/bin/tcsh', + orcaBuiltLine: true, + platform: 'darwin', + directory + }) + expect(staged.command).toBe(`/bin/sh '${staged.scriptPath}'`) + } + ) + it('types a short Orca-built line in a shell that reads its quoting literally', () => { const command = `claude 'wow!! great'` expect( diff --git a/src/shared/startup-command-staging.ts b/src/shared/startup-command-staging.ts index ff640f516d9..15e9d98db68 100644 --- a/src/shared/startup-command-staging.ts +++ b/src/shared/startup-command-staging.ts @@ -32,9 +32,9 @@ export type StartupCommandStaging = { failure?: string } -// Why only these type a short line as is: Orca's portable quoting is verified literal in these. Any -// other shell (tcsh, nu, xonsh, pwsh...) runs an agent launch line Orca built through `/bin/sh` -// instead; a command the user wrote for that shell is typed as it always was. +// Why only these type any short line as is: Orca's portable quoting is verified literal in these. Any +// other shell (tcsh, nu, xonsh, pwsh...) runs an agent launch line Orca built through `/bin/sh` when +// it holds anything but plain characters; a command the user wrote for that shell is typed as it was. const STAGING_SHELLS = new Set(['bash', 'zsh', 'sh', 'dash', 'fish', 'ksh', 'mksh']) // Why not ksh: it runs a sourced file's commands in the shell's own process group, so Ctrl-Z could @@ -63,17 +63,15 @@ export function shouldStageStartupCommand(args: { return false } const shellName = stagingShellName(args.shellPath) - if (shellName === null) { - // Why every length: Orca's POSIX quoting is literal only in the shells above (tcsh doubles a - // quoted backslash and expands `!!`), so another shell only ever sees the script's inert path. - return args.orcaBuiltLine === true - } const body = stripSubmitTerminator(args.command) - return ( - (hasControlByte(body) || - encoder.encode(body).byteLength > TYPED_STARTUP_LINE_PROMPT_BUDGET_BYTES) && - (SOURCING_SHELLS.has(shellName) || args.orcaBuiltLine === true) - ) + const needsStaging = + hasControlByte(body) || encoder.encode(body).byteLength > TYPED_STARTUP_LINE_PROMPT_BUDGET_BYTES + if (shellName === null) { + // Why these characters: tcsh doubles a quoted backslash and expands `!!`, and nu cannot read + // `'\''`. A plain line is typed so the agent is the shell's own job, where the paste guard finds it. + return args.orcaBuiltLine === true && (needsStaging || /[!\\"`$]/.test(body)) + } + return needsStaging && (SOURCING_SHELLS.has(shellName) || args.orcaBuiltLine === true) } function stagedScriptLine(shellName: string | null, quotedPath: string): string {