Add startup delivery diagnostics and success announcements

Terminal sessions now report startup command delivery details (whether written, presence, length, and delivery method) without logging the command text—preventing credential leakage and distinguishing missing commands from lost ones in diagnostics.

Setup scripts now announce completion on both POSIX and Windows before executing the startup command, so healthy setups don't appear stuck in the UI with "Waiting for setup..." as the last visible line.

Diagnostics failures are caught and ignored so they never break session creation.
This commit is contained in:
Jinjing
2026-08-31 22:39:01 -07:00
parent e3757a1d30
commit 77c2cd7356
4 changed files with 146 additions and 2 deletions
@@ -150,7 +150,23 @@ async function spawnAndPublishSession(
deps.onSessionCreated(opts.sessionId, opts.agentSessionGeneration, session.isAlive)
const token = session.attachClient(opts.streamClient)
if (opts.command && !subprocess.startupCommandDeliveredInShellArgs) {
const startupCommandWritten =
Boolean(opts.command) && !subprocess.startupCommandDeliveredInShellArgs
// Why: without this, a missing command and a lost one log identically.
// Length, never the text -- launches can carry credentials.
try {
deps.reportReadinessEvent?.('startup-command-delivery', {
sessionId: opts.sessionId,
written: startupCommandWritten,
hasCommand: Boolean(opts.command),
commandLength: opts.command?.length ?? 0,
viaShellArgs: subprocess.startupCommandDeliveredInShellArgs === true,
queuedByShellReadyBarrier: shellReadySupported
})
} catch {
// Diagnostics must never turn a live PTY into a failed create.
}
if (startupCommandWritten && opts.command) {
const submit = process.platform === 'win32' ? '\r' : '\n'
// Why: only Orca-wrapped shells advertise the paste-safe startup barrier.
session.write(
@@ -54,3 +54,79 @@ describe('TerminalHost startup command terminator', () => {
expect(sub.write).toHaveBeenCalledWith(sent)
})
})
// Why: a missing command and a lost one used to log identically.
describe('TerminalHost startup command delivery logging', () => {
let sub: SubprocessHandle
let events: { event: string; details: Record<string, unknown> }[]
let host: TerminalHost
beforeEach(() => {
sub = mockSubprocess()
events = []
host = new TerminalHost({
spawnSubprocess: () => sub,
reportReadinessEvent: (event, details) => events.push({ event, details })
})
})
const delivery = (): Record<string, unknown> =>
events.find((e) => e.event === 'startup-command-delivery')?.details ?? {}
it('records a written startup command', async () => {
await host.createOrAttach({
sessionId: 'delivery-written',
cols: 80,
rows: 24,
command: 'codex',
shellReadySupported: false,
streamClient: { onData: vi.fn(), onExit: vi.fn() }
})
expect(delivery()).toMatchObject({ written: true, hasCommand: true, commandLength: 5 })
})
it('records a session created with no startup command at all', async () => {
await host.createOrAttach({
sessionId: 'delivery-none',
cols: 80,
rows: 24,
shellReadySupported: false,
streamClient: { onData: vi.fn(), onExit: vi.fn() }
})
expect(delivery()).toMatchObject({ written: false, hasCommand: false, commandLength: 0 })
expect(sub.write).not.toHaveBeenCalled()
})
it('never logs the command text, which can carry credentials', async () => {
await host.createOrAttach({
sessionId: 'delivery-secret',
cols: 80,
rows: 24,
command: 'deploy --token=hunter2',
shellReadySupported: false,
streamClient: { onData: vi.fn(), onExit: vi.fn() }
})
expect(JSON.stringify(delivery())).not.toContain('hunter2')
})
it('still delivers the command when the diagnostic sink throws', async () => {
host = new TerminalHost({
spawnSubprocess: () => sub,
reportReadinessEvent: () => {
throw new Error('log sink unavailable')
}
})
await expect(
host.createOrAttach({
sessionId: 'delivery-sink-failure',
cols: 80,
rows: 24,
command: 'codex',
shellReadySupported: false,
streamClient: { onData: vi.fn(), onExit: vi.fn() }
})
).resolves.toMatchObject({ isNew: true })
expect(sub.write).toHaveBeenCalledWith(`codex${process.platform === 'win32' ? '\r' : '\n'}`)
})
})
+47
View File
@@ -9,6 +9,7 @@ import { getDefaultRepoHookSettings } from './constants'
import {
createSequencedSetupAgentCommands,
createSetupAgentSequenceNonce,
SETUP_COMPLETE_MESSAGE,
getSetupAgentSequenceShellForTests,
resolveSetupAgentSequenceLaunchCommand,
SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV,
@@ -89,6 +90,52 @@ describe('createSequencedSetupAgentCommands', () => {
)
})
it('announces success so the pane stops showing the waiting line', () => {
const commands = createSequencedSetupAgentCommands({
runnerScriptPath: '/repo/.git/orca/setup-runner.sh',
startupCommand: 'codex',
platform: 'posix',
nonce: 'nonce-1'
})
const script = commands.startupEnv?.ORCA_SEQUENCED_STARTUP_SCRIPT ?? ''
// Why ordering: `eval`/`exec` never returns, so a later message never renders.
expect(script.indexOf(SETUP_COMPLETE_MESSAGE)).toBeGreaterThan(-1)
expect(script.indexOf(SETUP_COMPLETE_MESSAGE)).toBeLessThan(
script.indexOf('eval "$ORCA_SEQUENCED_STARTUP_COMMAND"')
)
})
it('announces success on the native Windows gate too', () => {
const commands = createSequencedSetupAgentCommands({
runnerScriptPath: 'C:\\repo\\.git\\orca\\setup-runner.cmd',
platform: 'windows',
startupCommand: 'codex',
nonce: 'nonce-2'
})
const decoded = Buffer.from(
commands.startupCommand.split('-EncodedCommand ')[1] ?? '',
'base64'
).toString('utf16le')
expect(decoded).toContain(SETUP_COMPLETE_MESSAGE)
expect(decoded.indexOf(SETUP_COMPLETE_MESSAGE)).toBeLessThan(
decoded.indexOf('Invoke-Expression $startup')
)
})
it('leaves the failure and timeout messages as the only other outcomes', () => {
const script =
createSequencedSetupAgentCommands({
runnerScriptPath: '/repo/.git/orca/setup-runner.sh',
startupCommand: 'codex',
platform: 'posix',
nonce: 'nonce-3'
}).startupEnv?.ORCA_SEQUENCED_STARTUP_SCRIPT ?? ''
// Silence on success is what made a healthy worktree look stuck.
expect(script).toContain(SETUP_COMPLETE_MESSAGE)
expect(script).toContain('Setup failed; skipping agent startup.')
expect(script).toContain('Timed out waiting for setup before starting agent.')
})
it('keeps the POSIX terminal submission below the canonical input floor', () => {
const result = createSequencedSetupAgentCommands({
runnerScriptPath: `/repo/${'nested-worktree/'.repeat(100)}setup-runner.sh`,
+6 -1
View File
@@ -8,6 +8,8 @@ import {
} from './setup-runner-command'
const DEFAULT_WAIT_TIMEOUT_SECONDS = 2 * 60 * 60
// Exported so the gate and its tests share one definition.
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'
@@ -126,7 +128,9 @@ function buildPosixStartupScript(
`IFS=: read -r seen status < ${marker} || true;`,
`if [ "$seen" = ${nonceValue} ]; then`,
`rm -f ${marker} ${tmp} 2>/dev/null;`,
`if [ "$status" = "0" ]; then if [ -n "\${${SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV}:-}" ]; then eval "\$${SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV}"; exit "$?"; else ${startupSuccessCommand}; fi; fi;`,
// Why: failure and timeout announce themselves; a silent success left
// "Waiting for setup..." as the pane's last line forever.
`if [ "$status" = "0" ]; then echo ${quotePosixArg(SETUP_COMPLETE_MESSAGE)} >&2; if [ -n "\${${SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV}:-}" ]; then eval "\$${SETUP_AGENT_SEQUENCE_STARTUP_COMMAND_ENV}"; exit "$?"; else ${startupSuccessCommand}; fi; fi;`,
'echo "Setup failed; skipping agent startup." >&2;',
'exit "${status:-1}";',
'fi;',
@@ -247,6 +251,7 @@ function buildWindowsStartupCommand(
' [Console]::Error.WriteLine("Missing sequenced startup command.")',
' exit 1',
' }',
` [Console]::Error.WriteLine(${quotePowerShellString(SETUP_COMPLETE_MESSAGE)})`,
' Invoke-Expression $startup',
' if ($global:LASTEXITCODE -ne $null) { exit $global:LASTEXITCODE }',
' if (-not $?) { exit 1 }',