fix(worktrees): preserve gated agent startup on long paths

This commit is contained in:
Brennan Benson
2026-08-04 19:33:02 -07:00
parent 39c3c58d55
commit c9cf9975ac
4 changed files with 136 additions and 53 deletions
+39 -14
View File
@@ -112,7 +112,10 @@ import {
} from '../../shared/constants'
import { advertisedUrlWatcher } from '../ports/advertised-url-watcher'
import { makePaneKey } from '../../shared/stable-pane-id'
import { SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV } from '../../shared/setup-agent-sequencing'
import {
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV
} from '../../shared/setup-agent-sequencing'
import type {
AgentSessionExecutionClaim,
AgentSessionSurfaceBinding
@@ -5674,7 +5677,9 @@ describe('OrcaRuntimeService', () => {
1,
expect.objectContaining({
cwd: '/remote/mobile-setup',
command: expect.stringContaining('exec claude'),
env: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: expect.stringContaining('exec claude')
}),
worktreeId: result.worktree.id
})
)
@@ -5688,14 +5693,20 @@ describe('OrcaRuntimeService', () => {
worktreeId: result.worktree.id
})
)
const startupCommand = (spawn.mock.calls[0]![0] as { command: string }).command
const startup = spawn.mock.calls[0]![0] as {
command: string
env: Record<string, string>
}
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 nonceMatch = startupCommand.match(/if \[ "\$seen" = ([0-9a-f-]+) \]/)
const nonceMatch = startupScript.match(/if \[ "\$seen" = ([0-9a-f-]+) \]/)
expect(nonceMatch?.[1]).toBeTruthy()
const markerPath = `/remote/repo/.git/worktrees/mobile-setup/orca/setup-runner.sh.${nonceMatch![1]}.done`
expect(startupCommand.length).toBeLessThan(256)
expect(setupCommand).toContain('printf')
expect(setupCommand).toContain(`${nonceMatch![1]} "$status"`)
expect(startupCommand).toContain(markerPath)
expect(startupScript).toContain(markerPath)
expect(setupCommand).toContain(markerPath)
expect(revealTerminalSession).toHaveBeenLastCalledWith(
result.worktree.id,
@@ -39833,12 +39844,18 @@ describe('OrcaRuntimeService', () => {
)
// Why: setup is provisioned fire-and-forget; the wait-for-setup guarantee comes from the shell nonce/marker, not JS spawn ordering.
await vi.waitFor(() => expect(spawn).toHaveBeenCalledTimes(2))
const startupCommand = (spawn.mock.calls[0]![0] as { command: string }).command
const startup = spawn.mock.calls[0]![0] as {
command: string
env: Record<string, string>
}
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 nonceMatch = startupCommand.match(/if \[ "\$seen" = ([0-9a-f-]+) \]/)
const nonceMatch = startupScript.match(/if \[ "\$seen" = ([0-9a-f-]+) \]/)
expect(nonceMatch?.[1]).toBeTruthy()
expect(startupCommand).toContain('exec claude')
expect(startupCommand).toContain('/mnt/c/tmp/repo/.git/orca/setup-runner.sh')
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"`)
@@ -41117,7 +41134,9 @@ describe('OrcaRuntimeService', () => {
1,
expect.objectContaining({
cwd: '/tmp/workspaces/runtime-startup-setup-split',
command: expect.stringContaining('codex'),
env: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]: expect.stringContaining('codex')
}),
worktreeId: result.worktree.id
})
)
@@ -41134,15 +41153,21 @@ describe('OrcaRuntimeService', () => {
worktreeId: result.worktree.id
})
)
const startupCommand = (spawn.mock.calls[0]![0] as { command: string }).command
const startup = spawn.mock.calls[0]![0] as {
command: string
env: Record<string, string>
}
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 nonceMatch = startupCommand.match(/if \[ "\$seen" = ([0-9a-f-]+) \]/)
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).toContain('--dangerously-bypass-approvals-and-sandbox')
expect(startupCommand.length).toBeLessThan(256)
expect(startupScript).toContain('--dangerously-bypass-approvals-and-sandbox')
expect(setupCommand).toContain('printf')
expect(setupCommand).toContain(`${nonceMatch![1]} "$status"`)
expect(startupCommand).toContain(markerPath)
expect(startupScript).toContain(markerPath)
expect(setupCommand).toContain(markerPath)
const mainEnv = (spawn.mock.calls[0]![0] as { env?: Record<string, string> }).env ?? {}
const setupEnv = (spawn.mock.calls[1]![0] as { env?: Record<string, string> }).env ?? {}
@@ -1,6 +1,7 @@
/* eslint-disable max-lines -- Why: these activation cases share one mock store and assert ordering across startup, setup, issue commands, and default tabs. */
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { SetupScriptLaunchMode } from '../../../shared/types'
import { SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV } from '../../../shared/setup-agent-sequencing'
import { activateAndRevealWorktree, ensureWorktreeHasInitialTerminal } from './worktree-activation'
import { resetHookCommandDelayedDeliveryForTests } from './hook-command-delayed-delivery'
import { useAppStore } from '@/store'
@@ -698,13 +699,19 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command: expect.stringContaining('Timed out waiting for setup before starting agent.')
env: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: expect.stringContaining(
'Timed out waiting for setup before starting agent.'
)
})
})
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command: expect.stringContaining('exec claude')
env: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: expect.stringContaining('exec claude')
})
})
)
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
@@ -765,7 +772,9 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command: expect.stringContaining('exec claude')
env: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: expect.stringContaining('exec claude')
})
})
)
expect(store.queueTabSetupSplit).toHaveBeenCalledWith('tab-1', {
@@ -799,7 +808,11 @@ describe('ensureWorktreeHasInitialTerminal', () => {
expect(store.queueTabStartupCommand).toHaveBeenCalledWith(
'tab-1',
expect.objectContaining({
command: expect.stringContaining('/mnt/c/repo/.git/orca/setup-runner.sh')
env: expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: expect.stringContaining(
'/mnt/c/repo/.git/orca/setup-runner.sh'
)
})
})
)
expect(store.queueTabSetupSplit).toHaveBeenCalledWith('tab-1', {
+67 -26
View File
@@ -11,7 +11,8 @@ import {
createSetupAgentSequenceNonce,
getSetupAgentSequenceShellForTests,
resolveSetupAgentSequenceLaunchCommand,
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV
} from './setup-agent-sequencing'
import {
DEFAULT_SETUP_AGENT_STARTUP_POLICY,
@@ -66,23 +67,42 @@ describe('createSequencedSetupAgentCommands', () => {
expect(result.setupCommand).toContain(
'mv -f /repo/.git/orca/setup-runner.sh.nonce-123.done.tmp'
)
expect(result.startupCommand).toMatch(/^bash -lc /)
expect(result.startupCommand).toContain('deadline=$((SECONDS + 9))')
expect(result.startupCommand).not.toContain('date +%s')
expect(result.startupCommand).toContain('Waiting for setup to finish before starting agent...')
expect(result.startupCommand).toContain('[ "$seen" = nonce-123 ]')
expect(result.startupCommand).toContain(
const startupScript = result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]
expect(result.startupCommand).toBe(
`bash -lc 'eval "$${SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV}"'`
)
expect(startupScript).toContain('deadline=$((SECONDS + 9))')
expect(startupScript).not.toContain('date +%s')
expect(startupScript).toContain('Waiting for setup to finish before starting agent...')
expect(startupScript).toContain('[ "$seen" = nonce-123 ]')
expect(startupScript).toContain(
'rm -f /repo/.git/orca/setup-runner.sh.nonce-123.done /repo/.git/orca/setup-runner.sh.nonce-123.done.tmp'
)
expect(result.startupCommand).toContain('exec codex')
expect(result.startupCommand).toContain('fix bug')
expect(startupScript).toContain('exec codex')
expect(startupScript).toContain('fix bug')
expect(result.startupEnv).toEqual(
expect.objectContaining({
[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]: "codex 'fix bug'"
[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]: "codex 'fix bug'",
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: startupScript
})
)
})
it('keeps the POSIX terminal submission below the canonical input floor', () => {
const result = createSequencedSetupAgentCommands({
runnerScriptPath: `/repo/${'nested-worktree/'.repeat(100)}setup-runner.sh`,
startupCommand: 'codex',
platform: 'posix',
nonce: 'long-path'
})
expect(result.startupCommand.length).toBeLessThan(256)
expect(result.startupCommand).not.toContain('nested-worktree')
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
'nested-worktree'
)
})
it('uses launch-specific marker paths for overlapping setup gates', () => {
const first = createSequencedSetupAgentCommands({
runnerScriptPath: '/repo/.git/orca/setup-runner.sh',
@@ -98,9 +118,13 @@ describe('createSequencedSetupAgentCommands', () => {
})
expect(first.setupCommand).toContain('/repo/.git/orca/setup-runner.sh.first-launch.done')
expect(first.startupCommand).toContain('/repo/.git/orca/setup-runner.sh.first-launch.done')
expect(first.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
'/repo/.git/orca/setup-runner.sh.first-launch.done'
)
expect(second.setupCommand).toContain('/repo/.git/orca/setup-runner.sh.second-launch.done')
expect(second.startupCommand).toContain('/repo/.git/orca/setup-runner.sh.second-launch.done')
expect(second.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
'/repo/.git/orca/setup-runner.sh.second-launch.done'
)
expect(first.setupCommand).not.toContain('/repo/.git/orca/setup-runner.sh.second-launch.done')
expect(second.setupCommand).not.toContain('/repo/.git/orca/setup-runner.sh.first-launch.done')
})
@@ -114,8 +138,9 @@ describe('createSequencedSetupAgentCommands', () => {
waitTimeoutSeconds: 9
})
expect(result.startupCommand).toContain("exec codex '\\''fix this; then test'\\''")
expect(result.startupCommand).not.toContain('eval codex')
const startupScript = result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]
expect(startupScript).toContain("exec codex 'fix this; then test'")
expect(startupScript).not.toContain('eval codex')
})
it('preserves POSIX inline environment assignment startup commands', () => {
@@ -127,9 +152,10 @@ describe('createSequencedSetupAgentCommands', () => {
waitTimeoutSeconds: 9
})
expect(result.startupCommand).toContain('FOO=bar claude')
expect(result.startupCommand).toContain('exit "$?"')
expect(result.startupCommand).not.toContain('exec FOO=bar claude')
const startupScript = result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]
expect(startupScript).toContain('FOO=bar claude')
expect(startupScript).toContain('exit "$?"')
expect(startupScript).not.toContain('exec FOO=bar claude')
})
it('uses the converted Linux marker path for WSL UNC runners on Windows', () => {
@@ -162,7 +188,9 @@ describe('createSequencedSetupAgentCommands', () => {
expect(result.setupCommand).toContain(
'bash /remote/repo/.git/worktrees/feature/orca/setup-runner.sh'
)
expect(result.startupCommand).toContain('[ "$seen" = nonce-remote ]')
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
'[ "$seen" = nonce-remote ]'
)
})
it('preserves WSL shell metadata when sequencing native Windows runners', () => {
@@ -178,7 +206,7 @@ describe('createSequencedSetupAgentCommands', () => {
expect(result.setupCommand).toContain(
'/mnt/c/repo/.git/orca/setup-runner.sh.nonce-wsl-shell.done'
)
expect(result.startupCommand).toContain(
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
'/mnt/c/repo/.git/orca/setup-runner.sh.nonce-wsl-shell.done'
)
})
@@ -242,12 +270,14 @@ describe('createSequencedSetupAgentCommands', () => {
expect(result.setupCommand).toMatch(/^bash -lc /)
expect(result.startupCommand).toMatch(/^bash -lc /)
expect(result.startupCommand).not.toContain('Invoke-Expression')
expect(result.startupCommand).toContain('eval "$ORCA_SEQUENCED_STARTUP_COMMAND"')
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
'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(
'/c/repo/.git/orca/setup-runner.cmd.nonce-gitbash-cmd.done'
)
expect(result.startupCommand).toContain(
expect(result.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]).toContain(
'/c/repo/.git/orca/setup-runner.cmd.nonce-gitbash-cmd.done'
)
})
@@ -336,7 +366,10 @@ describe('createSequencedSetupAgentCommands', () => {
})
const startupExitPromise = waitForExit(
spawn('bash', ['-lc', commands.startupCommand], { stdio: 'pipe' })
spawn('bash', ['-lc', commands.startupCommand], {
stdio: 'pipe',
env: { ...process.env, ...commands.startupEnv }
})
)
await sleep(250)
expect(readIfExists(logPath)).toBe('')
@@ -380,15 +413,19 @@ describe('createSequencedSetupAgentCommands', () => {
spawn('bash', ['-lc', commands.setupCommand], { stdio: 'pipe' })
)
const startupExit = await waitForExit(
spawn('bash', ['-lc', commands.startupCommand], { stdio: 'pipe' })
spawn('bash', ['-lc', commands.startupCommand], {
stdio: 'pipe',
env: { ...process.env, ...commands.startupEnv }
})
)
const setupExit = await setupExitPromise
expect(setupExit.code).toBe(0)
expect(startupExit.code).toBe(0)
expect(readFileSync(logPath, 'utf8')).toBe('setup-done\nagent-start\ncleanup\n')
expect(commands.startupCommand).toContain('eval')
expect(commands.startupCommand).not.toContain('exec printf')
const startupScript = commands.startupEnv?.[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]
expect(startupScript).toContain('eval')
expect(startupScript).not.toContain('exec printf')
}
)
@@ -430,6 +467,7 @@ describe('createSequencedSetupAgentCommands', () => {
stdio: 'pipe',
env: {
...process.env,
...commands.startupEnv,
[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]: `FOO=bar bash ${quoteSh(startupScriptPath)}; printf 'env-cleanup\\n' >> ${quoteSh(logPath)}`
}
})
@@ -459,7 +497,10 @@ describe('createSequencedSetupAgentCommands', () => {
})
const startupExit = await waitForExit(
spawn('bash', ['-lc', commands.startupCommand], { stdio: 'pipe' })
spawn('bash', ['-lc', commands.startupCommand], {
stdio: 'pipe',
env: { ...process.env, ...commands.startupEnv }
})
)
expect(startupExit.code).toBe(124)
+13 -9
View File
@@ -9,6 +9,7 @@ import {
const DEFAULT_WAIT_TIMEOUT_SECONDS = 2 * 60 * 60
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 type SequencedSetupAgentCommands = {
setupCommand: string
@@ -69,16 +70,19 @@ export function createSequencedSetupAgentCommands(args: {
}
}
const startupScript = buildPosixStartupScript(
args.startupCommand,
markerPath,
nonce,
waitTimeoutSeconds
)
return {
setupCommand: buildPosixSetupCommand(resolution.command, markerPath, nonce),
startupCommand: buildPosixStartupCommand(
args.startupCommand,
markerPath,
nonce,
waitTimeoutSeconds
),
// Why: long worktree paths can push the gate past a PTY's canonical input cap and drop its submit byte.
startupCommand: `bash -lc 'eval "$${SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV}"'`,
startupEnv: {
[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]: args.startupCommand
[SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV]: args.startupCommand,
[SETUP_AGENT_SEQUENCE_STARTUP_SCRIPT_ENV]: startupScript
}
}
}
@@ -100,7 +104,7 @@ function buildPosixSetupCommand(setupCommand: string, markerPath: string, nonce:
return `bash -lc ${quotePosixArg(script)}`
}
function buildPosixStartupCommand(
function buildPosixStartupScript(
startupCommand: string,
markerPath: string,
nonce: string,
@@ -135,7 +139,7 @@ function buildPosixStartupCommand(
'done'
].join(' ')
return `bash -lc ${quotePosixArg(script)}`
return script
}
function buildPosixStartupSuccessCommand(startupCommand: string): string {