authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-31 15:10:16-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-31 15:10:16-07:00
logaa5521dcfb709ddd3f3d079e70efb71916e74fe4
treef92e62e1412b75f01adfea803c2215c0b988ee84
parent2d03847ccd53ffcd5ba74301ce85aef2afe58ad5
signature Signed by PGP key B5690EEEBB952194

Map fatal Codex stderr to runtime errors (#1615)


2 files changed, 63 insertions(+), 8 deletions(-)

apps/server/src/provider/Layers/CodexAdapter.test.ts+36
...@@ -508,6 +508,42 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {...@@ -508,6 +508,42 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
508 }),508 }),
509 );509 );
510510
511 it.effect("maps fatal websocket stderr notifications to runtime.error", () =>
512 Effect.gen(function* () {
513 const adapter = yield* CodexAdapter;
514 const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild);
515
516 lifecycleManager.emit("event", {
517 id: asEventId("evt-process-stderr-websocket"),
518 kind: "notification",
519 provider: "codex",
520 threadId: asThreadId("thread-1"),
521 createdAt: new Date().toISOString(),
522 method: "process/stderr",
523 turnId: asTurnId("turn-1"),
524 message:
525 "2026-03-31T18:14:06.833399Z ERROR codex_api::endpoint::responses_websocket: failed to connect to websocket: HTTP error: 503 Service Unavailable, url: wss://chatgpt.com/backend-api/codex/responses",
526 } satisfies ProviderEvent);
527
528 const firstEvent = yield* Fiber.join(firstEventFiber);
529
530 assert.equal(firstEvent._tag, "Some");
531 if (firstEvent._tag !== "Some") {
532 return;
533 }
534 assert.equal(firstEvent.value.type, "runtime.error");
535 if (firstEvent.value.type !== "runtime.error") {
536 return;
537 }
538 assert.equal(firstEvent.value.turnId, "turn-1");
539 assert.equal(firstEvent.value.payload.class, "provider_error");
540 assert.equal(
541 firstEvent.value.payload.message,
542 "2026-03-31T18:14:06.833399Z ERROR codex_api::endpoint::responses_websocket: failed to connect to websocket: HTTP error: 503 Service Unavailable, url: wss://chatgpt.com/backend-api/codex/responses",
543 );
544 }),
545 );
546
511 it.effect("preserves request type when mapping serverRequest/resolved", () =>547 it.effect("preserves request type when mapping serverRequest/resolved", () =>
512 Effect.gen(function* () {548 Effect.gen(function* () {
513 const adapter = yield* CodexAdapter;549 const adapter = yield* CodexAdapter;
apps/server/src/provider/Layers/CodexAdapter.ts+27-8
...@@ -112,6 +112,13 @@ function asNumber(value: unknown): number | undefined {...@@ -112,6 +112,13 @@ function asNumber(value: unknown): number | undefined {
112 return typeof value === "number" && Number.isFinite(value) ? value : undefined;112 return typeof value === "number" && Number.isFinite(value) ? value : undefined;
113}113}
114114
115const FATAL_CODEX_STDERR_SNIPPETS = ["failed to connect to websocket"];
116
117function isFatalCodexProcessStderrMessage(message: string): boolean {
118 const normalized = message.toLowerCase();
119 return FATAL_CODEX_STDERR_SNIPPETS.some((snippet) => normalized.includes(snippet));
120}
121
115function normalizeCodexTokenUsage(value: unknown): ThreadTokenUsageSnapshot | undefined {122function normalizeCodexTokenUsage(value: unknown): ThreadTokenUsageSnapshot | undefined {
116 const usage = asObject(value);123 const usage = asObject(value);
117 const totalUsage = asObject(usage?.total_token_usage ?? usage?.total);124 const totalUsage = asObject(usage?.total_token_usage ?? usage?.total);
...@@ -1269,15 +1276,27 @@ function mapToRuntimeEvents(...@@ -1269,15 +1276,27 @@ function mapToRuntimeEvents(
1269 }1276 }
12701277
1271 if (event.method === "process/stderr") {1278 if (event.method === "process/stderr") {
1279 const message = event.message ?? "Codex process stderr";
1280 const isFatal = isFatalCodexProcessStderrMessage(message);
1272 return [1281 return [
1273 {1282 isFatal
1274 type: "runtime.warning",1283 ? {
1275 ...runtimeEventBase(event, canonicalThreadId),1284 type: "runtime.error",
1276 payload: {1285 ...runtimeEventBase(event, canonicalThreadId),
1277 message: event.message ?? "Codex process stderr",1286 payload: {
1278 ...(event.payload !== undefined ? { detail: event.payload } : {}),1287 message,
1279 },1288 class: "provider_error" as const,
1280 },1289 ...(event.payload !== undefined ? { detail: event.payload } : {}),
1290 },
1291 }
1292 : {
1293 type: "runtime.warning",
1294 ...runtimeEventBase(event, canonicalThreadId),
1295 payload: {
1296 message,
1297 ...(event.payload !== undefined ? { detail: event.payload } : {}),
1298 },
1299 },
1281 ];1300 ];
1282 }1301 }
12831302