fix(terminal): quote agent resume for the tab's real Windows shell (cmd.exe) (#12476)

* fix(terminal): quote agent resume commands for the tab's real Windows shell

Cold restore and sleeping-agent resume built their launch line without the
host shell family, so win32 fell back to PowerShell argv quoting. On cmd.exe
tabs those quotes arrived literally and agent CLIs rejected the resume argv
and permission flags after a reboot ("unexpected argument ''<uuid>'' found").

Both call sites now share resolveAgentResumeLaunchTarget, which resolves the
launch platform and the live shell family together via
resolveLocalWindowsAgentStartupShell, honoring a per-tab shell override for
cold restore and leaving SSH / remote-runtime / WSL workspaces on their own
default quoting.

Fixes #12320

Co-authored-by: Orca <help@stably.ai>

* test(shared): cover cmd.exe resume quoting at the plan layer

Adopted from #12321 by @CountClaw.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil 2026-08-07 17:57:15 -07:00 committed by GitHub
parent 094d6821ef
commit 940f2ff1e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 550 additions and 30 deletions

View File

@ -8361,6 +8361,124 @@ describe('connectPanePty', () => {
}
})
// Regression (#12320): a cold restore after reboot typed PowerShell single quotes into
// cmd.exe tabs, so the agent CLI rejected the resume argv ("unexpected argument").
async function runWindowsColdRestoreResume(args: {
terminalWindowsShell: string
tabShellOverride?: string
}): Promise<string | undefined> {
const restoreNavigator = temporarilySetNavigatorUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
)
const pendingTimeouts: (() => void)[] = []
const originalSetTimeout = globalThis.setTimeout
globalThis.setTimeout = vi.fn((fn: () => void) => {
pendingTimeouts.push(fn)
return 999 as unknown as ReturnType<typeof setTimeout>
}) as unknown as typeof setTimeout
try {
const { connectPanePty } = await import('./pty-connection')
const paneKey = makePaneKey('tab-1', LEAF_2)
let activePtyId: string | null = 'restored-session'
const transport = createMockTransport('restored-session')
transport.getPtyId.mockImplementation(() => activePtyId)
transport.disconnect.mockImplementation(() => {
activePtyId = null
})
transport.connect.mockImplementation(async (opts: { sessionId?: string }) => {
if (opts.sessionId) {
activePtyId = opts.sessionId
return {
id: opts.sessionId,
isReattach: true,
snapshot: undefined,
replay: undefined,
coldRestore: undefined
}
}
activePtyId = 'fresh-resume-pty'
const onPtySpawn = createdTransportOptions[0]?.onPtySpawn as
| ((ptyId: string) => void)
| undefined
onPtySpawn?.('fresh-resume-pty')
return 'fresh-resume-pty'
})
transportFactoryQueue.push(transport)
mockStoreState = {
...mockStoreState,
tabsByWorktree: {
'wt-1': [
{
id: 'tab-1',
ptyId: 'restored-session',
...(args.tabShellOverride ? { shellOverride: args.tabShellOverride } : {})
}
]
},
settings: {
...mockStoreState.settings,
agentCmdOverrides: {},
terminalWindowsShell: args.terminalWindowsShell
},
sleepingAgentSessionsByPaneKey: {
[paneKey]: {
paneKey,
tabId: 'tab-1',
worktreeId: 'wt-1',
agent: 'codex',
providerSession: { key: 'session_id', id: 'codex-session-1' },
prompt: 'finish the task',
state: 'done',
origin: 'worktree-sleep',
capturedAt: 1,
updatedAt: 1
}
}
} as StoreState
const deps = createDeps({
restoredLeafId: LEAF_2,
restoredPtyIdByLeafId: { [LEAF_2]: 'restored-session' }
})
vi.mocked(window.api.pty.declarePendingPaneSerializer)
.mockResolvedValueOnce(1)
.mockResolvedValueOnce(2)
connectPanePty(createPane(2) as never, createManager(2) as never, deps as never)
await flushAsyncTicks(20)
for (const fn of pendingTimeouts) {
fn()
}
await flushAsyncTicks(10)
return (transport.connect.mock.calls.at(-1)?.[0] as { command?: string } | undefined)?.command
} finally {
globalThis.setTimeout = originalSetTimeout
restoreNavigator()
}
}
it('quotes a cold-restore resume command for a cmd.exe Windows tab', async () => {
await expect(runWindowsColdRestoreResume({ terminalWindowsShell: 'cmd.exe' })).resolves.toBe(
'codex "--dangerously-bypass-approvals-and-sandbox" "resume" "codex-session-1"'
)
})
it('prefers the tab shell override over the global Windows shell on cold restore', async () => {
await expect(
runWindowsColdRestoreResume({
terminalWindowsShell: 'powershell.exe',
tabShellOverride: 'cmd.exe'
})
).resolves.toBe('codex "--dangerously-bypass-approvals-and-sandbox" "resume" "codex-session-1"')
})
it('keeps PowerShell quoting for a cold-restore resume on a PowerShell Windows tab', async () => {
await expect(
runWindowsColdRestoreResume({ terminalWindowsShell: 'powershell.exe' })
).resolves.toBe("codex '--dangerously-bypass-approvals-and-sandbox' 'resume' 'codex-session-1'")
})
it('keeps a contentless reattach when the sleeping record represents a live session', async () => {
const { connectPanePty } = await import('./pty-connection')
const paneKey = makePaneKey('tab-1', LEAF_2)

View File

@ -284,6 +284,7 @@ import {
} from '@/lib/worktree-runtime-owner'
import { CLIENT_PLATFORM } from '@/lib/new-workspace'
import { buildAgentResumeStartupPlan } from '@/lib/tui-agent-startup'
import { resolveAgentResumeLaunchTarget } from '@/lib/agent-resume-launch-target'
import { resolveAgentStatusTerminalTitle } from '@/lib/agent-status-terminal-title'
import {
resolveTuiAgentLaunchArgs,
@ -308,7 +309,6 @@ import {
recognizeAgentProcessFromCommandLine
} from '../../../../shared/agent-process-recognition'
import type { SetupSplitDirection, TuiAgent } from '../../../../shared/types'
import { isWslUncPath } from '../../../../shared/wsl-paths'
import { isTuiAgent, TUI_AGENT_CONFIG } from '../../../../shared/tui-agent-config'
import { createDraftPasteReadyScanner } from '../../../../shared/draft-paste-ready-scanner'
import { sendAgentDraftPasteContent } from '@/lib/agent-draft-paste-content'
@ -4931,18 +4931,6 @@ export function connectPanePty(
sessionRestoredBannerShown = reason
deps.onShowSessionRestoredBanner(pane.id, reason)
}
const getColdRestoreAgentResumePlatform = (): NodeJS.Platform => {
if (projectRuntime?.status === 'repair-required') {
return projectRuntime.repair.preferredRuntime.kind === 'wsl' ? 'linux' : CLIENT_PLATFORM
}
if (projectRuntime?.status === 'resolved' && projectRuntime.runtime.kind === 'wsl') {
return 'linux'
}
if (connectionId || (worktree?.path && isWslUncPath(worktree.path))) {
return 'linux'
}
return CLIENT_PLATFORM
}
const buildColdRestoreAgentResumeStartup = (): ColdRestoreAgentResumeStartup | null => {
if (pendingStartupCommand) {
return null
@ -4975,7 +4963,16 @@ export function connectPanePty(
const launchConfig =
(useLiveEntry && entry ? state.getAgentLaunchConfigForStatusEntry(entry) : undefined) ??
matchingSleepingLaunchConfig
const resumePlatform = getColdRestoreAgentResumePlatform()
// Why: the resume line is typed into this pane's live shell, so its quoting must
// follow the tab's effective Windows shell, not the win32 PowerShell default.
const resumeTarget = resolveAgentResumeLaunchTarget({
projectRuntime,
connectionId,
executionHostId,
worktreePath: worktree?.path,
terminalWindowsShell: state.settings?.terminalWindowsShell,
tabShellOverride: shellOverride
})
const startupPlan = buildAgentResumeStartupPlan({
agent,
providerSession,
@ -4992,7 +4989,8 @@ export function connectPanePty(
...(launchConfig?.ompResumeFilePath
? { ompResumeFilePath: launchConfig.ompResumeFilePath }
: {}),
platform: resumePlatform
platform: resumeTarget.platform,
shell: resumeTarget.shell
})
if (!startupPlan) {
return null

View File

@ -0,0 +1,149 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { AgentResumeLaunchTargetArgs } from './agent-resume-launch-target'
function setNavigatorUserAgent(userAgent: string): () => void {
const original = Object.getOwnPropertyDescriptor(globalThis, 'navigator')
Object.defineProperty(globalThis, 'navigator', {
configurable: true,
value: {
platform: userAgent.includes('Windows') ? 'Win32' : 'MacIntel',
userAgent
}
})
return () => {
if (original) {
Object.defineProperty(globalThis, 'navigator', original)
} else {
delete (globalThis as { navigator?: Navigator }).navigator
}
}
}
const LOCAL_WINDOWS_ARGS: AgentResumeLaunchTargetArgs = {
projectRuntime: undefined,
connectionId: null,
executionHostId: 'local',
worktreePath: 'C:\\Users\\neil\\orca\\workspaces\\orca\\feature',
terminalWindowsShell: null
}
async function resolveWith(
overrides: Partial<AgentResumeLaunchTargetArgs>
): Promise<{ platform: NodeJS.Platform; shell: string | undefined }> {
const { resolveAgentResumeLaunchTarget } = await import('./agent-resume-launch-target')
return resolveAgentResumeLaunchTarget({
...LOCAL_WINDOWS_ARGS,
...overrides
})
}
describe('resolveAgentResumeLaunchTarget on a Windows client', () => {
let restoreNavigator = (): void => {}
beforeEach(() => {
vi.resetModules()
restoreNavigator = setNavigatorUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64)')
})
afterEach(() => {
restoreNavigator()
})
it('quotes for cmd.exe when the global Windows shell is cmd.exe', async () => {
await expect(resolveWith({ terminalWindowsShell: 'cmd.exe' })).resolves.toEqual({
platform: 'win32',
shell: 'cmd'
})
})
it('prefers the per-tab shell override over the global Windows shell', async () => {
await expect(
resolveWith({
terminalWindowsShell: 'powershell.exe',
tabShellOverride: 'C:\\WINDOWS\\system32\\cmd.exe'
})
).resolves.toEqual({ platform: 'win32', shell: 'cmd' })
})
it('quotes for POSIX on a Git Bash tab', async () => {
await expect(resolveWith({ terminalWindowsShell: 'git-bash' })).resolves.toEqual({
platform: 'win32',
shell: 'posix'
})
})
it('keeps PowerShell quoting when no Windows shell is configured', async () => {
await expect(resolveWith({})).resolves.toEqual({
platform: 'win32',
shell: 'powershell'
})
})
it('leaves an SSH workspace on its own default quoting', async () => {
await expect(
resolveWith({
connectionId: 'ssh-1',
executionHostId: 'ssh:ssh-1',
terminalWindowsShell: 'cmd.exe'
})
).resolves.toEqual({ platform: 'linux', shell: undefined })
})
it('does not describe a remote runtime host with the local Windows shell setting', async () => {
await expect(
resolveWith({
executionHostId: 'runtime:prod-box',
terminalWindowsShell: 'cmd.exe'
})
).resolves.toEqual({ platform: 'win32', shell: undefined })
})
it('keeps POSIX quoting for a WSL UNC worktree', async () => {
await expect(
resolveWith({
worktreePath: '\\\\wsl.localhost\\Ubuntu\\home\\neil\\repo',
terminalWindowsShell: 'cmd.exe'
})
).resolves.toEqual({ platform: 'linux', shell: undefined })
})
it('keeps POSIX quoting for a project pinned to a WSL runtime', async () => {
await expect(
resolveWith({
projectRuntime: {
status: 'resolved',
runtime: {
kind: 'wsl',
distro: 'Ubuntu',
projectId: 'repo-1',
reason: 'project-override',
cacheKey: 'repo-1:wsl:Ubuntu'
}
} as AgentResumeLaunchTargetArgs['projectRuntime'],
terminalWindowsShell: 'cmd.exe'
})
).resolves.toEqual({ platform: 'linux', shell: undefined })
})
})
describe('resolveAgentResumeLaunchTarget off Windows', () => {
let restoreNavigator = (): void => {}
beforeEach(() => {
vi.resetModules()
restoreNavigator = setNavigatorUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)')
})
afterEach(() => {
restoreNavigator()
})
it('ignores a stale Windows shell setting on a mac client', async () => {
await expect(
resolveWith({
worktreePath: '/Users/neil/repo',
terminalWindowsShell: 'cmd.exe'
})
).resolves.toEqual({ platform: 'darwin', shell: undefined })
})
})

View File

@ -0,0 +1,62 @@
import { CLIENT_PLATFORM } from '@/lib/new-workspace'
import { resolveWindowsShellOverride } from '@/lib/pane-manager/windows-pty-compatibility'
import { parseExecutionHostId } from '../../../shared/execution-host'
import { isWslUncPath } from '../../../shared/wsl-paths'
import { resolveLocalWindowsAgentStartupShell } from '../../../shared/windows-terminal-shell'
import type { ProjectExecutionRuntimeResolution } from '../../../shared/project-execution-runtime'
import type { AgentStartupShell } from '../../../shared/tui-agent-startup-shell'
export type AgentResumeLaunchTarget = {
platform: NodeJS.Platform
/** undefined keeps the platform default: PowerShell on win32, POSIX elsewhere. */
shell: AgentStartupShell | undefined
}
export type AgentResumeLaunchTargetArgs = {
projectRuntime: ProjectExecutionRuntimeResolution | undefined
/** SSH connection owning the workspace, if any. */
connectionId: string | null | undefined
/** Pane/workspace execution owner; only a 'local' host is the one `terminalWindowsShell` describes. */
executionHostId: string | null
worktreePath: string | null | undefined
terminalWindowsShell: string | null | undefined
/** Per-tab Windows shell override, which beats the global setting at spawn time. */
tabShellOverride?: string | null
}
function resolveResumeLaunchPlatform(args: AgentResumeLaunchTargetArgs): NodeJS.Platform {
if (args.projectRuntime?.status === 'repair-required') {
return args.projectRuntime.repair.preferredRuntime.kind === 'wsl' ? 'linux' : CLIENT_PLATFORM
}
if (args.projectRuntime?.status === 'resolved' && args.projectRuntime.runtime.kind === 'wsl') {
return 'linux'
}
if (args.connectionId || (args.worktreePath && isWslUncPath(args.worktreePath))) {
return 'linux'
}
return CLIENT_PLATFORM
}
/**
* Platform *and* live shell family a queued agent-resume command must be quoted for.
* Why the shell half matters: resume lines are typed into a real terminal, so on a
* cmd.exe tab the win32 PowerShell default sends literal quotes and the agent CLI
* rejects the resume argv ("unexpected argument ''<uuid>'' found", #12320).
*/
export function resolveAgentResumeLaunchTarget(
args: AgentResumeLaunchTargetArgs
): AgentResumeLaunchTarget {
const platform = resolveResumeLaunchPlatform(args)
return {
platform,
shell: resolveLocalWindowsAgentStartupShell({
platform,
isRemote:
Boolean(args.connectionId) || parseExecutionHostId(args.executionHostId)?.kind !== 'local',
terminalWindowsShell: resolveWindowsShellOverride(
args.tabShellOverride,
args.terminalWindowsShell
)
})
}
}

View File

@ -0,0 +1,157 @@
// Windows shell-quoting coverage for the sleeping-agent resume launch (#12320):
// the queued resume line is typed into the new tab's shell, so cmd.exe tabs must
// not receive PowerShell single quotes.
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { SleepingAgentSessionRecord } from '../../../shared/agent-session-resume'
const mockCreateTab = vi.fn()
const mockQueueTabStartupCommand = vi.fn()
const store = {
settings: {
agentCmdOverrides: {},
agentDefaultArgs: {} as Record<string, string>,
agentDefaultEnv: {} as Record<string, Record<string, string>>,
activeRuntimeEnvironmentId: null as string | null
} as {
agentCmdOverrides: Record<string, string>
agentDefaultArgs: Record<string, string>
agentDefaultEnv: Record<string, Record<string, string>>
activeRuntimeEnvironmentId: string | null
terminalWindowsShell?: string
},
repos: [
{
id: 'repo-1',
connectionId: null as string | null,
path: 'C:\\Users\\neil\\repo'
}
],
worktreesByRepo: {
'repo-1': [
{
id: 'wt-1',
repoId: 'repo-1',
path: 'C:\\Users\\neil\\repo\\feature',
displayName: 'feature'
}
]
} as Record<string, { id: string; repoId: string; path: string; displayName: string }[]>,
getKnownWorktreeById: (id: string) =>
Object.values(store.worktreesByRepo)
.flat()
.find((worktree) => worktree.id === id),
tabsByWorktree: { 'wt-1': [{ id: 'tab-1' }] },
openFiles: [] as { id: string; worktreeId: string }[],
browserTabsByWorktree: {} as Record<string, { id: string }[]>,
tabBarOrderByWorktree: {} as Record<string, string[]>,
createTab: mockCreateTab,
queueTabStartupCommand: mockQueueTabStartupCommand,
claimAutomaticAgentResume: vi.fn(),
clearSleepingAgentSession: vi.fn(),
setActiveTabType: vi.fn(),
setTabBarOrder: vi.fn()
}
vi.mock('@/store', () => ({ useAppStore: { getState: () => store } }))
vi.mock('@/lib/new-workspace', () => ({ CLIENT_PLATFORM: 'win32' }))
vi.mock('sonner', () => ({ toast: { message: vi.fn(), error: vi.fn() } }))
vi.mock('@/lib/telemetry', () => ({
track: vi.fn(),
tuiAgentToAgentKind: (agent: string) => agent
}))
vi.mock('@/components/tab-bar/reconcile-order', () => ({
reconcileTabOrder: vi.fn((_stored, termIds: string[]) => [...termIds])
}))
const SESSION_ID = '0199f7a1-0000-7000-8000-000000000001'
const record: SleepingAgentSessionRecord = {
paneKey: 'tab-1::leaf-1',
tabId: 'tab-1',
worktreeId: 'wt-1',
agent: 'codex',
providerSession: { key: 'session_id', id: SESSION_ID },
prompt: 'finish the task',
state: 'done',
origin: 'worktree-sleep',
capturedAt: 1,
updatedAt: 1
}
async function launch(): Promise<string | undefined> {
const { launchSleepingAgentSession } = await import('./sleeping-agent-session-launch')
launchSleepingAgentSession(record)
const queued = mockQueueTabStartupCommand.mock.calls.at(-1)?.[1] as
| { command: string }
| undefined
return queued?.command
}
describe('launchSleepingAgentSession Windows shell quoting', () => {
beforeEach(() => {
vi.clearAllMocks()
store.settings = {
agentCmdOverrides: {},
agentDefaultArgs: {},
agentDefaultEnv: {},
activeRuntimeEnvironmentId: null
}
store.repos = [{ id: 'repo-1', connectionId: null, path: 'C:\\Users\\neil\\repo' }]
store.worktreesByRepo = {
'repo-1': [
{
id: 'wt-1',
repoId: 'repo-1',
path: 'C:\\Users\\neil\\repo\\feature',
displayName: 'feature'
}
]
}
mockCreateTab.mockReturnValue({ id: 'tab-1' })
})
it('quotes the resume argv for a cmd.exe tab', async () => {
store.settings.terminalWindowsShell = 'cmd.exe'
await expect(launch()).resolves.toBe(
`codex "--dangerously-bypass-approvals-and-sandbox" "resume" "${SESSION_ID}"`
)
})
it('keeps PowerShell quoting for a powershell tab', async () => {
store.settings.terminalWindowsShell = 'powershell.exe'
await expect(launch()).resolves.toBe(
`codex '--dangerously-bypass-approvals-and-sandbox' 'resume' '${SESSION_ID}'`
)
})
it('quotes the resume argv for a Git Bash tab', async () => {
store.settings.terminalWindowsShell = 'git-bash'
await expect(launch()).resolves.toBe(
`codex '--dangerously-bypass-approvals-and-sandbox' 'resume' '${SESSION_ID}'`
)
})
it('ignores the local Windows shell setting for an SSH workspace', async () => {
store.settings.terminalWindowsShell = 'cmd.exe'
store.repos = [{ id: 'repo-1', connectionId: 'ssh-1', path: '/home/neil/repo' }]
store.worktreesByRepo = {
'repo-1': [
{
id: 'wt-1',
repoId: 'repo-1',
path: '/home/neil/repo/feature',
displayName: 'feature'
}
]
}
await expect(launch()).resolves.toBe(
`codex '--dangerously-bypass-approvals-and-sandbox' 'resume' '${SESSION_ID}'`
)
})
})

View File

@ -1,10 +1,13 @@
import { toast } from 'sonner'
import { useAppStore } from '@/store'
import { CLIENT_PLATFORM } from '@/lib/new-workspace'
import { buildAgentResumeStartupPlan } from '@/lib/tui-agent-startup'
import { tuiAgentToAgentKind } from '@/lib/telemetry'
import { reconcileTabOrder } from '@/components/tab-bar/reconcile-order'
import { isWslUncPath } from '../../../shared/wsl-paths'
import {
resolveAgentResumeLaunchTarget,
type AgentResumeLaunchTarget
} from '@/lib/agent-resume-launch-target'
import { getExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner'
import { getLocalProjectExecutionRuntimeContext } from '@/lib/local-preflight-context'
import {
resolveTuiAgentLaunchArgs,
@ -25,21 +28,18 @@ export type ResumeSleepingAgentSessionsOptions = {
onSessionLaunched?: (tabId: string) => void
}
function getResumeLaunchPlatform(worktreeId: string): NodeJS.Platform {
function getResumeLaunchTarget(worktreeId: string): AgentResumeLaunchTarget {
const state = useAppStore.getState()
const worktree = state.getKnownWorktreeById(worktreeId)
const repo = worktree ? state.repos.find((entry) => entry.id === worktree.repoId) : null
const projectRuntime = getLocalProjectExecutionRuntimeContext(state, worktreeId)
if (projectRuntime?.status === 'repair-required') {
return projectRuntime.repair.preferredRuntime.kind === 'wsl' ? 'linux' : CLIENT_PLATFORM
}
if (projectRuntime?.status === 'resolved' && projectRuntime.runtime.kind === 'wsl') {
return 'linux'
}
if (repo?.connectionId || (worktree?.path && isWslUncPath(worktree.path))) {
return 'linux'
}
return CLIENT_PLATFORM
// The resume tab is created without a shell override, so the global Windows shell wins.
return resolveAgentResumeLaunchTarget({
projectRuntime: getLocalProjectExecutionRuntimeContext(state, worktreeId),
connectionId: repo?.connectionId,
executionHostId: getExecutionHostIdForWorktree(state, worktreeId),
worktreePath: worktree?.path,
terminalWindowsShell: state.settings?.terminalWindowsShell
})
}
function appendTabToWorktreeOrder(worktreeId: string, tabId: string): void {
@ -68,6 +68,7 @@ export function launchSleepingAgentSession(
): boolean {
const state = useAppStore.getState()
const launchConfig = record.launchConfig
const resumeTarget = getResumeLaunchTarget(record.worktreeId)
const startupPlan = buildAgentResumeStartupPlan({
agent: record.agent,
providerSession: record.providerSession,
@ -84,7 +85,8 @@ export function launchSleepingAgentSession(
...(launchConfig?.ompResumeFilePath
? { ompResumeFilePath: launchConfig.ompResumeFilePath }
: {}),
platform: getResumeLaunchPlatform(record.worktreeId)
platform: resumeTarget.platform,
shell: resumeTarget.shell
})
if (!startupPlan) {
toast.error(

View File

@ -593,6 +593,40 @@ describe('tui agent startup plans', () => {
expect(plan?.launchCommand).toBe("codex 'resume' 's1'")
})
it('quotes Windows resume argv for cmd.exe when shell is cmd', () => {
const plan = buildAgentResumeStartupPlan({
agent: 'grok',
providerSession: { key: 'session_id', id: '019fc272-80fa-7a91-80a2-9c461ef1a9da' },
cmdOverrides: {},
agentArgs: '--permission-mode bypassPermissions',
platform: 'win32',
shell: 'cmd'
})
// Why: cmd.exe treats single quotes as literal characters. Resume must use
// double quotes (or unquoted tokens) so the CLI receives clean argv.
expect(plan?.launchCommand).toBe(
'grok "--permission-mode" "bypassPermissions" "--resume" "019fc272-80fa-7a91-80a2-9c461ef1a9da"'
)
})
it('keeps cmd-quoted agentCommand aligned with cmd resume suffix', () => {
const plan = buildAgentResumeStartupPlan({
agent: 'grok',
providerSession: { key: 'session_id', id: '019fc272-80fa-7a91-80a2-9c461ef1a9da' },
cmdOverrides: {},
agentCommand: 'grok "--permission-mode" "bypassPermissions"',
platform: 'win32',
shell: 'cmd'
})
// Regression: agentCommand from a prior cmd launch + PowerShell-default resume
// suffix produced mixed quoting and broke reboot restore on cmd.exe tabs.
expect(plan?.launchCommand).toBe(
'grok "--permission-mode" "bypassPermissions" "--resume" "019fc272-80fa-7a91-80a2-9c461ef1a9da"'
)
})
it('honors command overrides when building POSIX resume plans', () => {
const plan = buildAgentResumeStartupPlan({
agent: 'codex',