| ... | @@ -51,6 +51,41 @@ interface JsonRpcNotification { | ... | @@ -51,6 +51,41 @@ interface JsonRpcNotification { |
| 51 | params?: unknown; | 51 | params?: unknown; |
| 52 | } | 52 | } |
| 53 | | 53 | |
| | 54 | const ANSI_ESCAPE_CHAR = String.fromCharCode(27); |
| | 55 | const ANSI_ESCAPE_REGEX = new RegExp(`${ANSI_ESCAPE_CHAR}\\[[0-9;]*m`, "g"); |
| | 56 | const CODEX_STDERR_LOG_REGEX = |
| | 57 | /^\d{4}-\d{2}-\d{2}T\S+\s+(TRACE|DEBUG|INFO|WARN|ERROR)\s+\S+:\s+(.*)$/; |
| | 58 | const 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 | |
| | 63 | export 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 | |
| 54 | export interface CodexAppServerManagerEvents { | 89 | export interface CodexAppServerManagerEvents { |
| 55 | event: [event: ProviderEvent]; | 90 | event: [event: ProviderEvent]; |
| 56 | } | 91 | } |
| ... | @@ -267,12 +302,16 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve | ... | @@ -267,12 +302,16 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve |
| 267 | }); | 302 | }); |
| 268 | | 303 | |
| 269 | context.child.stderr.on("data", (chunk: Buffer) => { | 304 | context.child.stderr.on("data", (chunk: Buffer) => { |
| 270 | const message = chunk.toString().trim(); | 305 | const raw = chunk.toString(); |
| 271 | if (!message) { | 306 | const lines = raw.split(/\r?\n/g); |
| 272 | return; | 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); |
| 273 | } | 314 | } |
| 274 | | | |
| 275 | this.emitErrorEvent(context, "process/stderr", message); | | |
| 276 | }); | 315 | }); |
| 277 | | 316 | |
| 278 | context.child.on("error", (error) => { | 317 | context.child.on("error", (error) => { |