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 2f3129e9a4e..5a37dca8292 100644 --- a/src/shared/startup-command-staging.ts +++ b/src/shared/startup-command-staging.ts @@ -33,9 +33,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 @@ -62,15 +62,16 @@ export function shouldStageStartupCommand(args: { return false } const shellName = stagingShellName(args.shellPath) + const needsStaging = startupLineNeedsStaging(args.command) 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 + // 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(stripSubmitTerminator(args.command))) + ) } - return ( - startupLineNeedsStaging(args.command) && - (SOURCING_SHELLS.has(shellName) || args.orcaBuiltLine === true) - ) + return needsStaging && (SOURCING_SHELLS.has(shellName) || args.orcaBuiltLine === true) } /** Whether a line is too long or multi-line to type as it is, in a shell that can source a script. */