From aa5032f7273f1f8913a8cca960c05ab760df4b19 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:42:10 -0700 Subject: [PATCH] fix(runtime): handle unavailable foreground probes --- src/main/runtime/orca-runtime.test.ts | 21 +++++++++++++++++ src/main/runtime/orca-runtime.ts | 34 ++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index 0ff17fe1cd2..0531d427c26 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -8889,6 +8889,27 @@ describe('OrcaRuntimeService', () => { ) }) + it('treats synchronous foreground read failures as unavailable', async () => { + const { runtime, batches } = createSideEffectRuntime() + syncSinglePty(runtime) + runtime.ingestSyntheticTitleFrame('pty-1', '\x1b]0;Codex ready\x07') + const getForegroundProcess = vi.fn(() => { + throw new TypeError('getForegroundProcess is unavailable') + }) + runtime.setPtyController({ + write: () => true, + kill: () => true, + getForegroundProcess + }) + + runtime.onPtyData('pty-1', '\x1b]0;bichir\x07', 100) + + await vi.waitFor(() => + expect(batches.flatMap((batch) => batch.facts)).toContainEqual({ kind: 'agent-exited' }) + ) + expect(getForegroundProcess).toHaveBeenCalledOnce() + }) + it('aligns a restored session and pre-response bytes to the provider sequence', async () => { const { runtime } = createSideEffectRuntime() diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index fd17e657a5c..e06fda042ae 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -16610,20 +16610,42 @@ export class OrcaRuntimeService { } ) } - const entry: PtyForegroundProcessReadEntry = { + const unavailable: PtyForegroundProcessRead = { controller, - startedAfterTitleObservation: afterTitleObservation, - promise: Promise.resolve({ controller, process: null, available: false }) + process: null, + available: false } - entry.promise = controller - .getForegroundProcess(ptyId) + let processRead: Promise + try { + processRead = Promise.resolve(controller.getForegroundProcess(ptyId)) + } catch { + const entry: PtyForegroundProcessReadEntry = { + controller, + startedAfterTitleObservation: afterTitleObservation, + promise: Promise.resolve(unavailable) + } + entry.promise = entry.promise.finally(() => { + if (this.ptyForegroundProcessReads.get(ptyId) === entry) { + this.ptyForegroundProcessReads.delete(ptyId) + } + }) + this.ptyForegroundProcessReads.set(ptyId, entry) + return entry.promise + } + let entry: PtyForegroundProcessReadEntry + const promise = processRead .then((process) => ({ controller, process, available: true })) - .catch(() => ({ controller, process: null, available: false })) + .catch(() => unavailable) .finally(() => { if (this.ptyForegroundProcessReads.get(ptyId) === entry) { this.ptyForegroundProcessReads.delete(ptyId) } }) + entry = { + controller, + startedAfterTitleObservation: afterTitleObservation, + promise + } this.ptyForegroundProcessReads.set(ptyId, entry) return entry.promise }