From 981fb0fe8646e94322f19ce5c5e408730ecd6853 Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 3 Sep 2026 07:07:20 -0500 Subject: [PATCH] fix(runner): bind fast terminal turns deterministically --- .../src/drivers/acpx/runtime-host.test.ts | 20 +++++++++---------- .../src/live/runnerd-codex-transport.ts | 18 +++++++++++++++-- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts b/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts index f1b3199ec8..006bcf8005 100644 --- a/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts +++ b/packages/paperclip-runner/src/drivers/acpx/runtime-host.test.ts @@ -282,9 +282,7 @@ describe("ACPX runtime host", () => { expect(lifetimeFenceCandidates).toHaveLength(3); expect(new Set(lifetimeFenceCandidates).size).toBe(3); expect( - lifetimeFenceCandidates.every( - (port) => port >= 49_152 && port <= 65_535, - ), + lifetimeFenceCandidates.every((port) => port >= 49_152 && port <= 65_535), ).toBe(true); expect(capturedEnvironment.OPENAI_API_KEY).toBe("launch-secret"); expect(host.persistedEnvironment().OPENAI_API_KEY).toBeUndefined(); @@ -1255,13 +1253,15 @@ describe("ACPX runtime host", () => { }); // File removal precedes kernel lease release. Wait for the lease itself so // this assertion cannot race between those two ordered cleanup steps. - const contender = await vi.waitFor(() => - stageManagedCodexCredential({ - agentHomeDirectory: credentialHome, - environment: { - PAPERCLIP_ACPX_CODEX_AUTH_JSON_SECRET: '{"owner":"contender"}', - }, - }), + const contender = await vi.waitFor( + () => + stageManagedCodexCredential({ + agentHomeDirectory: credentialHome, + environment: { + PAPERCLIP_ACPX_CODEX_AUTH_JSON_SECRET: '{"owner":"contender"}', + }, + }), + { timeout: 5_000 }, ); await contender.close(); }); diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts index b013fd1986..29bd8f830d 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -3013,7 +3013,22 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { this.#flushPendingTraceRehydrations(); const events = this.#core?.store.state.committedEvents ?? []; while (this.#eventIndex < events.length) { - const event = events[this.#eventIndex++]; + const event = events[this.#eventIndex]!; + const eventPayload = record(event.envelope.payload).payload; + const terminalWhileTurnStartPending = + this.#turnStartResponsePending && + ([ + "turn.completed", + "turn.failed", + "turn.interrupted", + "turn.cancelled", + ].includes(event.eventType) || + (event.eventType === "provider.event" && + unwrapRunnerdProviderNotifications(eventPayload).some( + (notification) => notification.method === "turn/completed", + ))); + if (terminalWhileTurnStartPending) return; + this.#eventIndex += 1; if ( event.eventType === "harness.ready" || event.eventType === "session.started" || @@ -3141,7 +3156,6 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { this.#bridgedRuntimeInputs.delete(requestId); continue; } - const eventPayload = record(event.envelope.payload).payload; const sessionUpdatePayload = record(eventPayload); const canonicalMethod = ( {