-
Notifications
You must be signed in to change notification settings - Fork 298
fix: surface Codex model access blocked errors in step summary #18699
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,6 +122,42 @@ function extractMCPInitialization(lines) { | |
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Extract error messages from Codex logs (e.g., model access blocked, cyber_policy_violation) | ||
| * @param {string[]} lines - Array of log lines | ||
| * @returns {{hasErrors: boolean, messages: string[], reconnectCount: number, maxReconnects: number}} Error info | ||
| */ | ||
| function extractCodexErrorMessages(lines) { | ||
| const messages = new Set(); | ||
| let reconnectCount = 0; | ||
| let maxReconnects = 0; | ||
|
|
||
| for (const line of lines) { | ||
| // Match: ERROR: <message> (final error after all retries exhausted) | ||
| const errorMatch = line.match(/^ERROR:\s*(.+)$/); | ||
| if (errorMatch) { | ||
| messages.add(errorMatch[1].trim()); | ||
| } | ||
|
|
||
| // Match: Reconnecting... N/M (error message) - reconnect attempts with error details | ||
| const reconnectMatch = line.match(/^Reconnecting\.\.\.\s+(\d+)\/(\d+)\s*\((.+)\)$/); | ||
| if (reconnectMatch) { | ||
| const attempt = parseInt(reconnectMatch[1]); | ||
| const total = parseInt(reconnectMatch[2]); | ||
| if (attempt > reconnectCount) reconnectCount = attempt; | ||
| if (total > maxReconnects) maxReconnects = total; | ||
| messages.add(reconnectMatch[3].trim()); | ||
| } | ||
|
Comment on lines
+135
to
+150
|
||
| } | ||
|
|
||
| return { | ||
| hasErrors: messages.size > 0, | ||
| messages: Array.from(messages), | ||
| reconnectCount, | ||
| maxReconnects, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Convert parsed Codex data to logEntries format for plain text rendering | ||
| * @param {Array<{type: string, content?: string, toolName?: string, params?: string, response?: string, statusIcon?: string}>} parsedData - Parsed Codex log data | ||
|
|
@@ -255,6 +291,18 @@ function parseCodexLog(logContent) { | |
| markdown += mcpInfo.markdown; | ||
| } | ||
|
|
||
| // Extract error messages (e.g., model access blocked, cyber_policy_violation) | ||
| const errorInfo = extractCodexErrorMessages(lines); | ||
| if (errorInfo.hasErrors) { | ||
| markdown += "## ⚠️ Errors\n\n"; | ||
| for (const message of errorInfo.messages) { | ||
| markdown += `> ${message}\n\n`; | ||
| } | ||
| if (errorInfo.reconnectCount > 0) { | ||
| markdown += `> Reconnect attempts: ${errorInfo.reconnectCount}/${errorInfo.maxReconnects}\n\n`; | ||
| } | ||
| } | ||
|
Comment on lines
+294
to
+304
|
||
|
|
||
| markdown += "## 🤖 Reasoning\n\n"; | ||
|
|
||
| // Second pass: process full conversation flow with interleaved reasoning and tools | ||
|
|
@@ -662,5 +710,6 @@ if (typeof module !== "undefined" && module.exports) { | |
| formatCodexToolCall, | ||
| formatCodexBashCall, | ||
| extractMCPInitialization, | ||
| extractCodexErrorMessages, | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -542,4 +542,120 @@ I will analyze the code`; | |
| expect(result.markdown).toContain("## 🤖 Reasoning"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("extractCodexErrorMessages function", () => { | ||
| let extractCodexErrorMessages; | ||
|
|
||
| beforeEach(async () => { | ||
| const module = await import("./parse_codex_log.cjs"); | ||
| extractCodexErrorMessages = module.extractCodexErrorMessages; | ||
| }); | ||
|
Comment on lines
+549
to
+552
|
||
|
|
||
| it("should extract ERROR: lines", () => { | ||
| const lines = [ | ||
| "thinking", | ||
| "Some thinking content here", | ||
| "ERROR: stream disconnected before completion: This user's access to gpt-5.3-codex has been temporarily limited for potentially suspicious activity related to cybersecurity. Learn more about our safety mitigations: https://platform.openai.com/docs/guides/safety-checks/cybersecurity", | ||
| ]; | ||
|
|
||
| const result = extractCodexErrorMessages(lines); | ||
|
|
||
| expect(result.hasErrors).toBe(true); | ||
| expect(result.messages).toHaveLength(1); | ||
| expect(result.messages[0]).toContain("stream disconnected before completion"); | ||
| expect(result.messages[0]).toContain("cyber"); | ||
| }); | ||
|
|
||
| it("should extract Reconnecting messages with error details", () => { | ||
| const lines = [ | ||
| "Reconnecting... 1/5 (stream disconnected before completion: This user's access to gpt-5.3-codex has been temporarily limited for potentially suspicious activity related to cybersecurity. Learn more about our safety mitigations: https://platform.openai.com/docs/guides/safety-checks/cybersecurity)", | ||
| "Reconnecting... 2/5 (stream disconnected before completion: This user's access to gpt-5.3-codex has been temporarily limited for potentially suspicious activity related to cybersecurity. Learn more about our safety mitigations: https://platform.openai.com/docs/guides/safety-checks/cybersecurity)", | ||
| ]; | ||
|
|
||
| const result = extractCodexErrorMessages(lines); | ||
|
|
||
| expect(result.hasErrors).toBe(true); | ||
| expect(result.messages).toHaveLength(1); // De-duplicated | ||
| expect(result.reconnectCount).toBe(2); | ||
| expect(result.maxReconnects).toBe(5); | ||
| }); | ||
|
|
||
| it("should de-duplicate identical error messages from retries", () => { | ||
| const errorMsg = "stream disconnected before completion: This user's access to gpt-5.3-codex has been temporarily limited"; | ||
| const lines = [`Reconnecting... 1/5 (${errorMsg})`, `Reconnecting... 2/5 (${errorMsg})`, `Reconnecting... 3/5 (${errorMsg})`, `ERROR: ${errorMsg}`]; | ||
|
|
||
| const result = extractCodexErrorMessages(lines); | ||
|
|
||
| expect(result.hasErrors).toBe(true); | ||
| expect(result.messages).toHaveLength(1); | ||
| expect(result.reconnectCount).toBe(3); | ||
| }); | ||
|
|
||
| it("should return no errors for normal log lines", () => { | ||
| const lines = ["thinking", "I will list the open pull requests", "tool github.list_pull_requests({})"]; | ||
|
|
||
| const result = extractCodexErrorMessages(lines); | ||
|
|
||
| expect(result.hasErrors).toBe(false); | ||
| expect(result.messages).toHaveLength(0); | ||
| expect(result.reconnectCount).toBe(0); | ||
| expect(result.maxReconnects).toBe(0); | ||
| }); | ||
|
|
||
| it("should handle empty lines array", () => { | ||
| const result = extractCodexErrorMessages([]); | ||
|
|
||
| expect(result.hasErrors).toBe(false); | ||
| expect(result.messages).toHaveLength(0); | ||
| }); | ||
| }); | ||
|
|
||
| describe("parseCodexLog with model access errors", () => { | ||
| it("should include Errors section when ERROR: line is present", () => { | ||
| const logContent = `2026-02-27T14:06:41.886993Z INFO session_loop: codex_core::codex: Turn error: stream disconnected before completion: This user's access to gpt-5.3-codex has been temporarily limited for potentially suspicious activity related to cybersecurity. Learn more about our safety mitigations: https://platform.openai.com/docs/guides/safety-checks/cybersecurity | ||
| ERROR: stream disconnected before completion: This user's access to gpt-5.3-codex has been temporarily limited for potentially suspicious activity related to cybersecurity. Learn more about our safety mitigations: https://platform.openai.com/docs/guides/safety-checks/cybersecurity`; | ||
|
|
||
| const result = parseCodexLog(logContent); | ||
|
|
||
| expect(result.markdown).toContain("## ⚠️ Errors"); | ||
| expect(result.markdown).toContain("stream disconnected before completion"); | ||
| expect(result.markdown).toContain("cybersecurity"); | ||
| }); | ||
|
|
||
| it("should include Errors section with reconnect count when retries occurred", () => { | ||
| const logContent = `Reconnecting... 1/5 (stream disconnected before completion: This user's access to gpt-5.3-codex has been temporarily limited for potentially suspicious activity related to cybersecurity) | ||
| Reconnecting... 2/5 (stream disconnected before completion: This user's access to gpt-5.3-codex has been temporarily limited for potentially suspicious activity related to cybersecurity) | ||
| Reconnecting... 3/5 (stream disconnected before completion: This user's access to gpt-5.3-codex has been temporarily limited for potentially suspicious activity related to cybersecurity) | ||
| ERROR: stream disconnected before completion: This user's access to gpt-5.3-codex has been temporarily limited for potentially suspicious activity related to cybersecurity`; | ||
|
|
||
| const result = parseCodexLog(logContent); | ||
|
|
||
| expect(result.markdown).toContain("## ⚠️ Errors"); | ||
| expect(result.markdown).toContain("Reconnect attempts: 3/5"); | ||
| }); | ||
|
|
||
| it("should not include Errors section for normal logs", () => { | ||
| const logContent = `thinking | ||
| I will list the open pull requests | ||
| tool github.list_pull_requests({"state":"open"}) | ||
| github.list_pull_requests(...) success in 123ms: | ||
| {"items": []}`; | ||
|
|
||
| const result = parseCodexLog(logContent); | ||
|
|
||
| expect(result.markdown).not.toContain("## ⚠️ Errors"); | ||
| }); | ||
|
|
||
| it("should place Errors section before Reasoning section", () => { | ||
| const logContent = `ERROR: This user's access to gpt-5.3-codex has been temporarily limited`; | ||
|
|
||
| const result = parseCodexLog(logContent); | ||
|
|
||
| const errorsIndex = result.markdown.indexOf("## ⚠️ Errors"); | ||
| const reasoningIndex = result.markdown.indexOf("## 🤖 Reasoning"); | ||
| expect(errorsIndex).toBeGreaterThan(-1); | ||
| expect(reasoningIndex).toBeGreaterThan(-1); | ||
| expect(errorsIndex).toBeLessThan(reasoningIndex); | ||
| }); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extractCodexErrorMessagesusesparseInt(...)without a radix. It’s safer and consistent to pass base 10 (or useNumber(...)) even though the regex only matches digits.