diff --git a/apps/server/src/codexAppServerManager.test.ts b/apps/server/src/codexAppServerManager.test.ts index 680d9d9608..f614b92302 100644 --- a/apps/server/src/codexAppServerManager.test.ts +++ b/apps/server/src/codexAppServerManager.test.ts @@ -207,6 +207,42 @@ describe("classifyCodexStderrLine", () => { }); }); +describe("process stderr events", () => { + it("emits classified stderr lines as notifications", () => { + const manager = new CodexAppServerManager(); + const emitEvent = vi + .spyOn(manager as unknown as { emitEvent: (...args: unknown[]) => void }, "emitEvent") + .mockImplementation(() => {}); + + ( + manager as unknown as { + emitNotificationEvent: ( + context: { session: { threadId: ThreadId } }, + method: string, + message: string, + ) => void; + } + ).emitNotificationEvent( + { + session: { + threadId: asThreadId("thread-1"), + }, + }, + "process/stderr", + "fatal: permission denied", + ); + + expect(emitEvent).toHaveBeenCalledWith( + expect.objectContaining({ + kind: "notification", + method: "process/stderr", + threadId: "thread-1", + message: "fatal: permission denied", + }), + ); + }); +}); + describe("normalizeCodexModelSlug", () => { it("maps 5.3 aliases to gpt-5.3-codex", () => { expect(normalizeCodexModelSlug("5.3")).toBe("gpt-5.3-codex"); diff --git a/apps/server/src/codexAppServerManager.ts b/apps/server/src/codexAppServerManager.ts index 991a9783df..1f0abd6d73 100644 --- a/apps/server/src/codexAppServerManager.ts +++ b/apps/server/src/codexAppServerManager.ts @@ -1046,7 +1046,7 @@ export class CodexAppServerManager extends EventEmitter { }), ); + it.effect("maps process stderr notifications to runtime.warning", () => + Effect.gen(function* () { + const adapter = yield* CodexAdapter; + const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); + + lifecycleManager.emit("event", { + id: asEventId("evt-process-stderr"), + kind: "notification", + provider: "codex", + threadId: asThreadId("thread-1"), + createdAt: new Date().toISOString(), + method: "process/stderr", + turnId: asTurnId("turn-1"), + message: "The filename or extension is too long. (os error 206)", + } satisfies ProviderEvent); + + const firstEvent = yield* Fiber.join(firstEventFiber); + + assert.equal(firstEvent._tag, "Some"); + if (firstEvent._tag !== "Some") { + return; + } + assert.equal(firstEvent.value.type, "runtime.warning"); + if (firstEvent.value.type !== "runtime.warning") { + return; + } + assert.equal(firstEvent.value.turnId, "turn-1"); + assert.equal( + firstEvent.value.payload.message, + "The filename or extension is too long. (os error 206)", + ); + }), + ); + it.effect("preserves request type when mapping serverRequest/resolved", () => Effect.gen(function* () { const adapter = yield* CodexAdapter; diff --git a/apps/server/src/provider/Layers/CodexAdapter.ts b/apps/server/src/provider/Layers/CodexAdapter.ts index 9af5aac19d..4fcc7eddd1 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.ts @@ -1267,6 +1267,19 @@ function mapToRuntimeEvents( ]; } + if (event.method === "process/stderr") { + return [ + { + type: "runtime.warning", + ...runtimeEventBase(event, canonicalThreadId), + payload: { + message: event.message ?? "Codex process stderr", + ...(event.payload !== undefined ? { detail: event.payload } : {}), + }, + }, + ]; + } + if (event.method === "windows/worldWritableWarning") { return [ {