fix: prevent setup command truncation during shell startup (#25820)

* fix: keep setup command short so it survives shell startup

The POSIX setup wrapper inlined the runner path six times. On long
worktree paths it exceeded macOS's 1024-byte canonical PTY input limit
when typed while the shell was still sourcing rc files, so the tail and
submit byte were dropped, setup never ran, and a gated agent waited
forever.

Carry the setup script in ORCA_SEQUENCED_SETUP_SCRIPT and submit only
`bash -lc 'eval "$ORCA_SEQUENCED_SETUP_SCRIPT"'`, mirroring the
existing startup gate. The env is merged into setup.envVars wherever the
gate is built so provisioning, fallback retries and renderer tabs/splits
all receive it, and WSL imports it untranslated.

Closes #20935

* fix(wsl): forward the setup script WSLENV entry before the daemon boundary

Same-protocol daemons from older builds rebuild WSLENV without
ORCA_SEQUENCED_SETUP_SCRIPT, so WSL setup would eval an empty script and
the gated agent would wait for its timeout. Also swap new spec casts for
the spawnedPtyEnv fixture.

* fix: fail loudly when the sequenced setup script env is missing

A dropped ORCA_SEQUENCED_SETUP_SCRIPT made eval run an empty string and
exit 0, silently skipping setup. ${VAR:?missing} reports it in the Setup pane.

---------

Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
Eotel
2026-10-28 00:41:15 -07:00
committed by GitHub
co-authored by Neil
parent fdfc5e2569
commit c698f9bded
14 changed files with 144 additions and 82 deletions
@@ -593,6 +593,13 @@ describe('registerPtyHandlers', () => {
expect(env.GIT_CONFIG_KEY_0).toBe('http.proxy')
expect(env.GIT_CONFIG_KEY_1).toBeUndefined()
})
it('forwards the sequenced setup script into WSLENV before the daemon boundary', async () => {
const env = await withWin32Platform(() =>
daemonSpawnAndGetEnv({ ORCA_SEQUENCED_SETUP_SCRIPT: 'true', WSLENV: 'EXISTING/p' })
)
expect(env.WSLENV).toBe('EXISTING/p:ORCA_SEQUENCED_SETUP_SCRIPT')
})
})
})
})
+6
View File
@@ -3,8 +3,10 @@ import type { AgentProviderSessionMetadata } from '../../../shared/agent-session
import { getAppEnvironment } from '../../../shared/app-environment'
import type { GlobalSettings } from '../../../shared/global-settings-types'
import { stampRuntimeSourceEnv } from '../../../shared/runtime-source-env'
import { SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
import type { TuiAgent } from '../../../shared/tui-agent'
import { isTuiAgent } from '../../../shared/tui-agent-config'
import { addWslEnvKeys } from '../../../shared/wsl-env'
import { isAgentStatusHooksEnabled } from '../../agent-hooks/managed-agent-hook-controls'
import { awaitExplicitAgentPluginGuestReadiness } from '../../agent-hooks/wsl-agent-plugin-readiness'
import { ensureCodexStateDbBackfillRecoveryStarted } from '../../codex/codex-state-db-backfill-recovery'
@@ -245,6 +247,10 @@ export async function resolvePtySpawnHostEnv(args: PtySpawnHostEnvArgs): Promise
getRuntimeSourceStamp(deps.runtime, getAppEnvironment().getPath('userData'))
)
promoteAgentTeamsShimPath(env, args.requestedAgentTeamsPath)
if (process.platform === 'win32' && env[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]) {
// Why: same-protocol daemons from older builds don't add this WSLENV entry themselves.
addWslEnvKeys(env, [SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV])
}
} catch (err) {
// Host prep creates hook/extension state; only this request's minted ID is safe to sweep.
if (args.mintedSessionId) {
+3 -1
View File
@@ -317,7 +317,8 @@ async function spawnLocalStartupAndSetupTerminals(args: {
defaultTabs: CreateWorktreeResult['defaultTabs']
createdWithAgent: CreateWorktreeArgs['createdWithAgent']
}): Promise<StagedStartupResult> {
const { runtime, worktree, startup, setup, defaultTabs, createdWithAgent } = args
const { runtime, worktree, startup, defaultTabs, createdWithAgent } = args
let { setup } = args
if (!startup || defaultTabs?.tabs.length) {
return { didSpawnSetup: false }
}
@@ -345,6 +346,7 @@ async function spawnLocalStartupAndSetupTerminals(args: {
...(sequenced.startupEnv ? { env: { ...startup.env, ...sequenced.startupEnv } } : {})
}
wrappedSetupCommandStr = sequenced.setupCommand
setup = { ...setup, envVars: { ...setup.envVars, ...sequenced.setupEnv } }
}
try {
@@ -1,5 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { resolve } from 'node:path'
import { SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV } from '../../shared/setup-agent-sequencing'
import type { CreateWorktreeResult } from '../../shared/worktree/create-types'
import { resolveRegisteredWorktreePath } from './registered-worktree-roots-cache'
import { computeWorkspaceRootAsync } from './worktree-logic'
import type * as WorktreeLogic from './worktree-logic'
@@ -779,15 +781,17 @@ describe('registerWorktreeHandlers', () => {
request_kind: 'new'
}
}
})) as { setup?: { command?: string; runnerScriptPath: string } }
})) as { setup?: CreateWorktreeResult['setup'] }
expect(result.setup).toEqual(
expect.objectContaining({
runnerScriptPath: 'C:\\workspace\\repo\\.git\\orca\\setup-runner.sh',
command: expect.stringContaining('bash /mnt/c/workspace/repo/.git/orca/setup-runner.sh')
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`
})
)
expect(result.setup?.command).toContain('printf')
expect(result.setup?.envVars[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]).toContain(
'bash /mnt/c/workspace/repo/.git/orca/setup-runner.sh'
)
})
it('opens Setup beside the startup terminal without surfacing the workspace', async () => {
+6 -3
View File
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
import { isAbsolute } from 'node:path'
import { getShellReadyWrapperRoot } from '../providers/local-pty-shell-ready-wrapper-root'
import {
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV,
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV
} from '../../shared/setup-agent-sequencing'
@@ -46,10 +47,11 @@ describe('addOrcaWslInteropEnv', () => {
expect(env.WSLENV?.split(':')).toContain('ORCA_SHELL_READY_ROOT/p')
})
it('imports setup-gated startup env into WSL without path translation', () => {
it('imports setup-gated startup and setup env into WSL without path translation', () => {
const env: Record<string, string> = {
[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]: 'codex',
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: 'while :; do sleep 1; done'
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: 'while :; do sleep 1; done',
[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: '( bash /repo/setup-runner.sh )'
}
addOrcaWslInteropEnv(env)
@@ -57,7 +59,8 @@ describe('addOrcaWslInteropEnv', () => {
expect(env.WSLENV?.split(':')).toEqual([
'ORCA_SHELL_READY_ROOT/p',
`${SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV}/u`,
`${SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV}/u`
`${SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV}/u`,
`${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}/u`
])
})
+2
View File
@@ -9,6 +9,7 @@ import {
ORCHESTRATION_COMPATIBILITY_HOST_KIND_ENV
} from '../../shared/orchestration-compatibility-evidence'
import {
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV,
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV
} from '../../shared/setup-agent-sequencing'
@@ -98,6 +99,7 @@ export function addOrcaWslInteropEnv(env: Record<string, string>): void {
'ORCA_OPENCODE_AGENT/u',
`${SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV}/u`,
`${SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV}/u`,
`${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}/u`,
'ORCA_ORCHESTRATION_COMPATIBILITY_HOST_KIND/u',
'ORCA_ORCHESTRATION_COMPATIBILITY_HOST_ID/u',
'ORCA_ORCHESTRATION_COMPATIBILITY_HOST_INCARNATION/u',
@@ -1,5 +1,6 @@
import { getDefaultWorkspaceSession } from '../../../shared/constants'
import { describe, expect, it, vi } from 'vitest'
import { SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
import {
OrcaRuntimeService,
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV,
@@ -115,15 +116,19 @@ describe('OrcaRuntimeService', () => {
}
const startupCommand = startup.command
const startupScript = startup.env[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]!
const setupCommand = (spawn.mock.calls[1]![0] as { command: string }).command
const setupScript = spawnedPtyEnv(spawn.mock.calls[1]![0])[
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV
]
const nonceMatch = startupScript.match(/if \[ "\$seen" = ([0-9a-f-]+) \]/)
expect(nonceMatch?.[1]).toBeTruthy()
expect(startupCommand.length).toBeLessThan(256)
expect(startupScript).toContain('exec claude')
expect(startupScript).toContain('/mnt/c/tmp/repo/.git/orca/setup-runner.sh')
expect(setupCommand).toContain('bash /mnt/c/tmp/repo/.git/orca/setup-runner.sh')
expect(setupCommand).toContain('printf')
expect(setupCommand).toContain(`${nonceMatch![1]} "$status"`)
expect(spawn.mock.calls[1]![0]).toMatchObject({
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`
})
expect(setupScript).toContain('bash /mnt/c/tmp/repo/.git/orca/setup-runner.sh')
expect(setupScript).toContain(`${nonceMatch![1]} "$status"`)
expect(result.setup).toBeUndefined()
})
@@ -1,6 +1,7 @@
import type { RuntimeClientEvent } from '../../../shared/runtime-client-events'
import { getDefaultWorkspaceSession } from '../../../shared/constants'
import { describe, expect, it, vi } from 'vitest'
import { SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
import {
OrcaRuntimeService,
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
@@ -334,10 +335,13 @@ describe('OrcaRuntimeService', () => {
2,
expect.objectContaining({
cwd: '/tmp/workspaces/runtime-startup-setup-split',
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh'),
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`,
env: expect.objectContaining({
ORCA_ROOT_PATH: '/tmp/repo',
ORCA_WORKTREE_PATH: '/tmp/workspaces/runtime-startup-setup-split'
ORCA_WORKTREE_PATH: '/tmp/workspaces/runtime-startup-setup-split',
[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: expect.stringContaining(
'bash /tmp/repo/.git/orca/setup-runner.sh'
)
}),
worktreeId: result.worktree.id
})
@@ -348,16 +352,17 @@ describe('OrcaRuntimeService', () => {
}
const startupCommand = startup.command
const startupScript = startup.env[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]!
const setupCommand = (spawn.mock.calls[1]![0] as { command: string }).command
const setupScript = spawnedPtyEnv(spawn.mock.calls[1]![0])[
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV
]
const nonceMatch = startupScript.match(/if \[ "\$seen" = ([0-9a-f-]+) \]/)
expect(nonceMatch?.[1]).toBeTruthy()
const markerPath = `/tmp/repo/.git/orca/setup-runner.sh.${nonceMatch![1]}.done`
expect(startupCommand.length).toBeLessThan(256)
expect(startupScript).toContain('--dangerously-bypass-approvals-and-sandbox')
expect(setupCommand).toContain('printf')
expect(setupCommand).toContain(`${nonceMatch![1]} "$status"`)
expect(setupScript).toContain(`${nonceMatch![1]} "$status"`)
expect(startupScript).toContain(markerPath)
expect(setupCommand).toContain(markerPath)
expect(setupScript).toContain(markerPath)
const mainEnv = spawnedPtyEnv(spawn.mock.calls[0]![0])
const setupEnv = spawnedPtyEnv(spawn.mock.calls[1]![0])
expect(result.setup).toBeUndefined()
@@ -473,7 +478,12 @@ describe('OrcaRuntimeService', () => {
expect.any(String),
expect.objectContaining({
runnerScriptPath: 'C:\\tmp\\repo\\.git\\orca\\setup-runner.sh',
command: expect.stringContaining('bash /mnt/c/tmp/repo/.git/orca/setup-runner.sh')
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`,
envVars: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: expect.stringContaining(
'bash /mnt/c/tmp/repo/.git/orca/setup-runner.sh'
)
})
}),
undefined,
undefined
@@ -483,8 +493,6 @@ describe('OrcaRuntimeService', () => {
expect(
events.filter((event) => event.type === 'activateWorktree' && event.setup !== undefined)
).toEqual([])
const activationSetup = activateWorktree.mock.calls[0]?.[2] as { command?: string } | undefined
expect(activationSetup?.command).toContain('printf')
})
it('lets explicit startup draft agents override the desktop default', async () => {
@@ -72,7 +72,8 @@ export async function startRuntimeLocalWorktreeTerminals(args: {
warning?: string
ports: Ports
}): Promise<RuntimeLocalWorktreeTerminalStartupResult> {
const { request, repo, worktree, setup, defaultTabs, startup, ports } = args
const { request, repo, worktree, defaultTabs, startup, ports } = args
let { setup } = args
const shouldActivate = request.activate === true || request.runHooks === true
let warning = args.warning
let didSpawnStartup = false
@@ -98,6 +99,7 @@ export async function startRuntimeLocalWorktreeTerminals(args: {
...(sequenced.startupEnv ? { env: { ...startup.env, ...sequenced.startupEnv } } : {})
}
wrappedSetupCommand = sequenced.setupCommand
setup = { ...setup, envVars: { ...setup.envVars, ...sequenced.setupEnv } }
}
// Main owns failed agent startup too, keeping its reason visible without a terminal service.
@@ -162,7 +164,12 @@ export async function startRuntimeLocalWorktreeTerminals(args: {
Boolean(setup || defaultTabs))
if (runtimeWillProvision) {
const provisioned = await ports.provision({
...provisionArgs(args, startupTerminalHandle, startupPaneHeld, wrappedSetupCommand),
...provisionArgs(
{ ...args, setup },
startupTerminalHandle,
startupPaneHeld,
wrappedSetupCommand
),
...(provisionInBackground ? { surfaceOwner: false as const } : {})
})
didSpawnSetup = provisioned.setupSpawned
@@ -186,7 +193,12 @@ export async function startRuntimeLocalWorktreeTerminals(args: {
}
} else if (ports.canSpawn && (setup || defaultTabs || didSpawnStartup)) {
const provisioning = ports.provision({
...provisionArgs(args, startupTerminalHandle, startupPaneHeld, wrappedSetupCommand),
...provisionArgs(
{ ...args, setup },
startupTerminalHandle,
startupPaneHeld,
wrappedSetupCommand
),
surfaceOwner: false
})
if (request.awaitTerminalProvisioning) {
@@ -1,5 +1,8 @@
import { describe, expect, it, vi } from 'vitest'
import { SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
import {
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV,
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV
} from '../../../shared/setup-agent-sequencing'
import { ensureWorktreeHasInitialTerminal } from './worktree-initial-terminal-seeding'
import {
createMockStore,
@@ -10,6 +13,13 @@ import {
registerWorktreeActivationReset()
function sequencedSetup(env: Record<string, string>, runnerCommand: string): unknown {
return {
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`,
env: { ...env, [SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: expect.stringContaining(runnerCommand) }
}
}
describe('ensureWorktreeHasInitialTerminal', () => {
it('creates a background Setup tab for newly created worktrees by default', () => {
let createdIndex = 0
@@ -112,14 +122,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(result).toBe('tab-1')
expect(createTab).toHaveBeenCalledTimes(1)
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh')
})
)
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('printf')
})
sequencedSetup({ ORCA_ROOT_PATH: '/tmp/repo' }, 'bash /tmp/repo/.git/orca/setup-runner.sh')
)
})
@@ -176,9 +179,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
})
})
)
expect(startups['tab-3']).toEqual(
expect.objectContaining({ command: expect.stringContaining('bash /tmp/setup.sh') })
)
expect(startups['tab-3']).toEqual(sequencedSetup({}, 'bash /tmp/setup.sh'))
if (!activateCreatedTabs) {
expect(store.setActiveTab).not.toHaveBeenCalled()
}
@@ -219,14 +220,7 @@ describe('ensureWorktreeHasInitialTerminal', () => {
})
)
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('printf')
})
)
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh')
})
sequencedSetup({ ORCA_ROOT_PATH: '/tmp/repo' }, 'bash /tmp/repo/.git/orca/setup-runner.sh')
)
})
@@ -277,14 +271,9 @@ describe('ensureWorktreeHasInitialTerminal', () => {
})
})
)
expect(createdTabStartups(store)['tab-2']).toEqual({
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh'),
env: { ORCA_ROOT_PATH: '/tmp/repo' }
})
expect(createdTabStartups(store)['tab-2']).toEqual({
command: expect.stringContaining('printf'),
env: { ORCA_ROOT_PATH: '/tmp/repo' }
})
expect(createdTabStartups(store)['tab-2']).toEqual(
sequencedSetup({ ORCA_ROOT_PATH: '/tmp/repo' }, 'bash /tmp/repo/.git/orca/setup-runner.sh')
)
})
it('keeps WSL setup shell metadata when gating startup behind setup completion', () => {
@@ -312,10 +301,9 @@ describe('ensureWorktreeHasInitialTerminal', () => {
})
})
)
expect(createdTabStartups(store)['tab-2']).toEqual({
command: expect.stringContaining('bash /mnt/c/repo/.git/orca/setup-runner.sh'),
env: { ORCA_ROOT_PATH: 'C:\\repo' }
})
expect(createdTabStartups(store)['tab-2']).toEqual(
sequencedSetup({ ORCA_ROOT_PATH: 'C:\\repo' }, 'bash /mnt/c/repo/.git/orca/setup-runner.sh')
)
})
it.each(['split-vertical', 'split-horizontal', 'new-tab', null] as const)(
@@ -1,4 +1,5 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
import { activateAndRevealFolderWorkspace, activateAndRevealWorktree } from './worktree-activation'
import { ensureWorktreeHasInitialTerminal } from './worktree-initial-terminal-seeding'
import type { AppStoreState } from './worktree-activation.test-fixture'
@@ -300,16 +301,15 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(store.setTabCustomTitle).toHaveBeenCalledWith('tab-2', 'Setup', {
recordInteraction: false
})
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('bash /tmp/repo/.git/orca/setup-runner.sh')
})
)
expect(createdTabStartups(store)['tab-2']).toEqual(
expect.objectContaining({
command: expect.stringContaining('printf')
})
)
expect(createdTabStartups(store)['tab-2']).toEqual({
command: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`,
env: {
ORCA_ROOT_PATH: '/tmp/repo',
[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: expect.stringContaining(
'bash /tmp/repo/.git/orca/setup-runner.sh'
)
}
})
})
it('holds the issue command for the first mirrored web runtime tab when none exists yet', () => {
@@ -129,6 +129,7 @@ export function ensureWorktreeHasInitialTerminal(
...(sequenced.startupEnv ? { env: { ...startup.env, ...sequenced.startupEnv } } : {})
}
wrappedSetupCommandStr = sequenced.setupCommand
setup = { ...setup, envVars: { ...setup.envVars, ...sequenced.setupEnv } }
}
const backendStartupTerminalSpawned = opts?.backendStartupTerminalSpawned === true
+28 -10
View File
@@ -8,7 +8,9 @@ import { afterEach, describe, expect, it } from 'vitest'
import {
createSequencedSetupAgentCommands,
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV,
SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV,
type SequencedSetupAgentCommands
} from './setup-agent-sequencing'
const TEMP_DIRS: string[] = []
@@ -29,6 +31,9 @@ describe('createSequencedSetupAgentCommands', () => {
nonce: 'long-path'
})
expect(Buffer.byteLength(result.setupCommand)).toBeLessThan(256)
expect(result.setupCommand).not.toContain('nested-worktree')
expect(result.setupEnv?.[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]).toContain('nested-worktree')
expect(result.startupCommand.length).toBeLessThan(256)
expect(result.startupCommand).not.toContain('nested-worktree')
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
@@ -44,7 +49,7 @@ describe('createSequencedSetupAgentCommands', () => {
nonce: 'nonce-remote'
})
expect(result.setupCommand).toContain(
expect(setupScript(result)).toContain(
'bash /remote/repo/.git/worktrees/feature/orca/setup-runner.sh'
)
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
@@ -61,8 +66,8 @@ describe('createSequencedSetupAgentCommands', () => {
nonce: 'nonce-wsl-shell'
})
expect(result.setupCommand).toContain('bash /mnt/c/repo/.git/orca/setup-runner.sh')
expect(result.setupCommand).toContain(
expect(setupScript(result)).toContain('bash /mnt/c/repo/.git/orca/setup-runner.sh')
expect(setupScript(result)).toContain(
'/mnt/c/repo/.git/orca/setup-runner.sh.nonce-wsl-shell.done'
)
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
@@ -80,11 +85,11 @@ describe('createSequencedSetupAgentCommands', () => {
nonce: 'nonce-gitbash-cmd'
})
expect(result.setupCommand).toContain(
expect(setupScript(result)).toContain(
'powershell.exe -NoProfile -NonInteractive -EncodedCommand'
)
expect(result.setupCommand).not.toMatch(/bash\s+\S*setup-runner/)
expect(decodePowerShellScript(result.setupCommand)).toContain(
expect(decodePowerShellScript(setupScript(result))).toContain(
"$runner = 'C:\\repo\\.git\\orca\\setup-runner.cmd'"
)
// PowerShell Invoke-Expression cannot parse POSIX apostrophe escaping; bash must evaluate startup.
@@ -95,7 +100,7 @@ describe('createSequencedSetupAgentCommands', () => {
'eval "$ORCA_SEQUENCED_STARTUP_COMMAND"'
)
// Why: bash writes and reads the marker here, so it needs the /c/... form of the path.
expect(result.setupCommand).toContain(
expect(setupScript(result)).toContain(
'/c/repo/.git/orca/setup-runner.cmd.nonce-gitbash-cmd.done'
)
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
@@ -198,7 +203,10 @@ describe('createSequencedSetupAgentCommands', () => {
expect(readFileSync(markerPath, 'utf8')).toBe('stale:0\n')
const setupExit = await waitForExit(
spawn('bash', ['-lc', commands.setupCommand], { stdio: 'pipe' })
spawn('bash', ['-lc', commands.setupCommand], {
stdio: 'pipe',
env: { ...process.env, ...commands.setupEnv }
})
)
expect(setupExit.code).toBe(0)
@@ -232,7 +240,10 @@ describe('createSequencedSetupAgentCommands', () => {
})
const setupExitPromise = waitForExit(
spawn('bash', ['-lc', commands.setupCommand], { stdio: 'pipe' })
spawn('bash', ['-lc', commands.setupCommand], {
stdio: 'pipe',
env: { ...process.env, ...commands.setupEnv }
})
)
const startupExit = await waitForExit(
spawn('bash', ['-lc', commands.startupCommand], {
@@ -282,7 +293,10 @@ describe('createSequencedSetupAgentCommands', () => {
})
const setupExitPromise = waitForExit(
spawn('bash', ['-lc', commands.setupCommand], { stdio: 'pipe' })
spawn('bash', ['-lc', commands.setupCommand], {
stdio: 'pipe',
env: { ...process.env, ...commands.setupEnv }
})
)
const startupExit = await waitForExit(
spawn('bash', ['-lc', commands.startupCommand], {
@@ -331,6 +345,10 @@ describe('createSequencedSetupAgentCommands', () => {
)
})
function setupScript(commands: SequencedSetupAgentCommands): string {
return commands.setupEnv?.[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV] ?? commands.setupCommand
}
function makeTempDir(): string {
const dir = mkdtempSync(join(tmpdir(), 'orca-setup-sequencing-'))
TEMP_DIRS.push(dir)
+13 -7
View File
@@ -15,9 +15,11 @@ const DEFAULT_WAIT_TIMEOUT_SECONDS = 2 * 60 * 60
export const SETUP_COMPLETE_MESSAGE = 'Setup finished; starting agent.'
export const SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV = 'ORCA_SEQUENCED_STARTUP_COMMAND'
export const SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV = 'ORCA_SEQUENCED_STARTUP_SCRIPT'
export const SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV = 'ORCA_SEQUENCED_SETUP_SCRIPT'
export type SequencedSetupAgentCommands = {
setupCommand: string
setupEnv?: Record<string, string>
startupCommand: string
startupEnv?: Record<string, string>
}
@@ -75,8 +77,15 @@ export function createSequencedSetupAgentCommands(args: {
waitTimeoutSeconds
)
return {
setupCommand: buildPosixSetupCommand(resolution.command, markerPath, nonce),
// Why: long worktree paths can push the gate past a PTY's canonical input cap and drop its submit byte.
// Why: long worktree paths can push typed wrappers past a PTY's canonical input cap and drop their submit byte.
setupCommand: `bash -lc 'eval "\${${SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV}:?missing}"'`,
setupEnv: {
[SETUP_AGENT_SEQUENCE_SETUP_SCRIPT_ENV]: buildPosixSetupScript(
resolution.command,
markerPath,
nonce
)
},
startupCommand: `bash -lc 'eval "$${SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV}"'`,
startupEnv: {
[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]: args.startupCommand,
@@ -85,13 +94,12 @@ export function createSequencedSetupAgentCommands(args: {
}
}
function buildPosixSetupCommand(setupCommand: string, markerPath: string, nonce: string): string {
function buildPosixSetupScript(setupCommand: string, markerPath: string, nonce: string): string {
const marker = quotePosixShellUnlessPlainNoAt(markerPath)
const tmp = quotePosixShellUnlessPlainNoAt(`${markerPath}.tmp`)
const nonceValue = quotePosixShellUnlessPlainNoAt(nonce)
// Why no `( … )`: this is typed into the user's line editor, where autopair widgets corrupt it (#18059).
const script = [
return [
`rm -f ${marker} ${tmp} 2>/dev/null`,
setupCommand,
'status=$?',
@@ -99,8 +107,6 @@ function buildPosixSetupCommand(setupCommand: string, markerPath: string, nonce:
`mv -f ${tmp} ${marker}`,
'exit "$status"'
].join('; ')
return `bash -lc ${quotePosixShellUnlessPlainNoAt(script)}`
}
function buildPosixStartupScript(