From 0f507a8622fa7244b431db9c021acf4fd0350e48 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 21 Aug 2026 10:12:37 -0500 Subject: [PATCH] fix(runner): await authoritative provider turn identity --- .../src/live/runnerd-codex-transport.ts | 21 ++++++++++++++----- server/src/vendor/paperclip-runner/index.ts | 1 + 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts index 42c707d1905..ced8d2ddca2 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -394,9 +394,20 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { async #startTurn(params: Record): Promise> { const input = Array.isArray(params.input) ? params.input.map(record) : []; const message = input.map((item) => typeof item.text === "string" ? item.text : "").join("\n"); - this.#turnId = `turn_lab_${randomUUID().replaceAll("-", "")}`; + const pendingTurnId = `turn_lab_${randomUUID().replaceAll("-", "")}`; + this.#turnId = pendingTurnId; await this.#command("turn.start", { text: message }); - this.#pumpEvents(); + // Command completion only means runnerd accepted the command. Codex assigns + // the authoritative turn identity in the subsequent turn/started event, so + // do not expose the temporary transport identity to the strict driver. + const deadline = Date.now() + 30_000; + while (this.#turnId === pendingTurnId && Date.now() < deadline) { + this.#pumpEvents(); + if (this.#turnId !== pendingTurnId) break; + if (this.#handle?.child.exitCode !== null) throw new Error("runnerd exited before provider turn startup"); + await new Promise((resolveWait) => setTimeout(resolveWait, 10)); + } + if (this.#turnId === pendingTurnId) throw new Error("runnerd did not report the provider turn identity"); return { turn: { id: this.#turnId, status: "inProgress" } }; } @@ -476,9 +487,9 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { const method = payload.method; if (typeof method !== "string") continue; const params = record(payload.params); - if (method === "turn/started") { - const providerTurnId = record(params.turn).id; - if (typeof providerTurnId === "string") this.#turnId = providerTurnId; + const providerTurnId = params.turnId ?? record(params.turn).id; + if (typeof providerTurnId === "string" && providerTurnId.length > 0) { + this.#turnId = providerTurnId; } this.#queue.push({ method, params }); } diff --git a/server/src/vendor/paperclip-runner/index.ts b/server/src/vendor/paperclip-runner/index.ts index 5bcb2829aeb..e307e3d257c 100644 --- a/server/src/vendor/paperclip-runner/index.ts +++ b/server/src/vendor/paperclip-runner/index.ts @@ -2,4 +2,5 @@ // the server after changing runner internals; the workspace export resolves through dist. The // server build replaces this compiled shim with the package's built distribution, so published // server installs do not need a separate paperclip-runner npm bootstrap. +// Keep runtime behavior in the package; this file is only the server build boundary. export * from "@paperclipai/paperclip-runner";