authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-07 20:42:35-08:00
committergravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-07 20:42:35-08:00
log13501e85f61f2b004c3dc2819ccfb237fe30e305
treee8d4bb9ac36919ce4040a088cee1e3a445d32a92
parent0aefe6d10a1da279bc5ff78c3b40ac6ab6e011dd
signature Commit is signed but in an unrecognized format.

working kind of


2 files changed, 80 insertions(+), 5 deletions(-)

apps/desktop/src/codexAppServerManager.test.ts created+36
......@@ -0,0 +1,36 @@
1import { describe, expect, it } from "vitest";
2
3import { classifyCodexStderrLine } from "./codexAppServerManager";
4
5describe("classifyCodexStderrLine", () => {
6 it("ignores empty lines", () => {
7 expect(classifyCodexStderrLine(" ")).toBeNull();
8 });
9
10 it("ignores non-error structured codex logs", () => {
11 const line =
12 "2026-02-08T04:24:19.241256Z WARN codex_core::features: unknown feature key in config: skills";
13 expect(classifyCodexStderrLine(line)).toBeNull();
14 });
15
16 it("ignores known benign rollout path errors", () => {
17 const line =
18 "\u001b[2m2026-02-08T04:24:20.085687Z\u001b[0m \u001b[31mERROR\u001b[0m \u001b[2mcodex_core::rollout::list\u001b[0m: state db missing rollout path for thread 019c3b6c-46b8-7b70-ad23-82f824d161fb";
19 expect(classifyCodexStderrLine(line)).toBeNull();
20 });
21
22 it("keeps unknown structured errors", () => {
23 const line =
24 "2026-02-08T04:24:20.085687Z ERROR codex_core::runtime: unrecoverable failure";
25 expect(classifyCodexStderrLine(line)).toEqual({
26 message: line,
27 });
28 });
29
30 it("keeps plain stderr messages", () => {
31 const line = "fatal: permission denied";
32 expect(classifyCodexStderrLine(line)).toEqual({
33 message: line,
34 });
35 });
36});
apps/desktop/src/codexAppServerManager.ts+44-5
......@@ -51,6 +51,41 @@ interface JsonRpcNotification {
5151 params?: unknown;
5252}
5353
54const ANSI_ESCAPE_CHAR = String.fromCharCode(27);
55const ANSI_ESCAPE_REGEX = new RegExp(`${ANSI_ESCAPE_CHAR}\\[[0-9;]*m`, "g");
56const CODEX_STDERR_LOG_REGEX =
57 /^\d{4}-\d{2}-\d{2}T\S+\s+(TRACE|DEBUG|INFO|WARN|ERROR)\s+\S+:\s+(.*)$/;
58const BENIGN_ERROR_LOG_SNIPPETS = [
59 "state db missing rollout path for thread",
60 "state db record_discrepancy: find_thread_path_by_id_str_in_subdir, falling_back",
61];
62
63export function classifyCodexStderrLine(
64 rawLine: string,
65): { message: string } | null {
66 const line = rawLine.replaceAll(ANSI_ESCAPE_REGEX, "").trim();
67 if (!line) {
68 return null;
69 }
70
71 const match = line.match(CODEX_STDERR_LOG_REGEX);
72 if (match) {
73 const level = match[1];
74 if (level && level !== "ERROR") {
75 return null;
76 }
77
78 const isBenignError = BENIGN_ERROR_LOG_SNIPPETS.some((snippet) =>
79 line.includes(snippet),
80 );
81 if (isBenignError) {
82 return null;
83 }
84 }
85
86 return { message: line };
87}
88
5489export interface CodexAppServerManagerEvents {
5590 event: [event: ProviderEvent];
5691}
......@@ -267,12 +302,16 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
267302 });
268303
269304 context.child.stderr.on("data", (chunk: Buffer) => {
270 const message = chunk.toString().trim();
271 if (!message) {
272 return;
305 const raw = chunk.toString();
306 const lines = raw.split(/\r?\n/g);
307 for (const rawLine of lines) {
308 const classified = classifyCodexStderrLine(rawLine);
309 if (!classified) {
310 continue;
311 }
312
313 this.emitErrorEvent(context, "process/stderr", classified.message);
273314 }
274
275 this.emitErrorEvent(context, "process/stderr", message);
276315 });
277316
278317 context.child.on("error", (error) => {