diff --git a/actions/setup/js/log_parser_format.cjs b/actions/setup/js/log_parser_format.cjs index 99384688ced..cf68ccbed05 100644 --- a/actions/setup/js/log_parser_format.cjs +++ b/actions/setup/js/log_parser_format.cjs @@ -585,6 +585,12 @@ function createLogParserFormatters(deps) { if (lastEntry?.total_cost_usd) { lines.push(` Cost: $${lastEntry.total_cost_usd.toFixed(4)}`); } + if (lastEntry?.errors && Array.isArray(lastEntry.errors) && lastEntry.errors.length > 0) { + lines.push(" Errors:"); + for (const error of lastEntry.errors) { + lines.push(` ${error}`); + } + } } function generateSummaryLines(logEntries) { diff --git a/actions/setup/js/parse_codex_log.cjs b/actions/setup/js/parse_codex_log.cjs index 98ccf5aa102..8b2c532c344 100644 --- a/actions/setup/js/parse_codex_log.cjs +++ b/actions/setup/js/parse_codex_log.cjs @@ -409,14 +409,32 @@ function parseCodexJsonl(logContent) { }); break; } + case "error": { + const rawMessage = typeof item.message === "string" ? item.message : JSON.stringify(item); + const message = rawMessage.trim(); + if (message) { + parsedData.push({ type: "error", content: message }); + } + break; + } default: break; } } + const errorMessages = parsedData.filter(item => item.type === "error").map(item => item.content); + // Build markdown so the parser returns a truthy result and core.info has a // readable fallback. The step summary itself is rendered from logEntries. - let markdown = "
\nReasoning\n\n"; + let markdown = ""; + if (errorMessages.length > 0) { + markdown += "
\nErrors\n\n"; + for (const message of errorMessages) { + markdown += `> ${message}\n\n`; + } + markdown += "
\n\n"; + } + markdown += "
\nReasoning\n\n"; for (const item of parsedData) { if (item.type === "text") { markdown += `${item.content}\n\n`; @@ -456,17 +474,21 @@ function parseCodexJsonl(logContent) { model: model || undefined, }); - // Surface token usage and turn count via a result entry so Statistics and the - // OTEL telemetry enrichment (agent-stdio.log result line) are populated. - if (usage) { + // Surface token usage, turn count, and error messages via a result entry so + // Statistics, the Information section's Errors list, and the OTEL telemetry + // enrichment (agent-stdio.log result line) are populated. + if (usage || errorMessages.length > 0) { logEntries.push({ type: "result", num_turns: turnCount > 0 ? turnCount : undefined, - usage: { - input_tokens: typeof usage.input_tokens === "number" ? usage.input_tokens : undefined, - output_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : undefined, - cache_read_input_tokens: typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : undefined, - }, + usage: usage + ? { + input_tokens: typeof usage.input_tokens === "number" ? usage.input_tokens : undefined, + output_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : undefined, + cache_read_input_tokens: typeof usage.cached_input_tokens === "number" ? usage.cached_input_tokens : undefined, + } + : undefined, + errors: errorMessages.length > 0 ? errorMessages : undefined, }); } diff --git a/actions/setup/js/parse_codex_log.test.cjs b/actions/setup/js/parse_codex_log.test.cjs index 74b0f3c37ed..df0fc4083d0 100644 --- a/actions/setup/js/parse_codex_log.test.cjs +++ b/actions/setup/js/parse_codex_log.test.cjs @@ -9,6 +9,7 @@ describe("parse_codex_log.cjs", () => { let estimateTokens; let formatDuration; let extractMCPInitialization; + let generateCopilotCliStyleSummary; beforeEach(async () => { // Mock core actions methods @@ -38,6 +39,7 @@ describe("parse_codex_log.cjs", () => { truncateString = sharedModule.truncateString; estimateTokens = sharedModule.estimateTokens; formatDuration = sharedModule.formatDuration; + generateCopilotCliStyleSummary = sharedModule.generateCopilotCliStyleSummary; }); describe("parseCodexLog function", () => { @@ -833,5 +835,18 @@ ERROR: This user's access to o4-mini has been temporarily limited`; expect(result.markdown).toContain("Reviewed the issue"); expect(result.markdown).toContain("Total Tokens Used:"); }); + + it("surfaces item errors in the result and rendered step summary without usage", () => { + const errorMessage = "Model metadata unavailable; using fallback metadata."; + const errorOnlyLog = ['{"type":"thread.started","thread_id":"019ef8cb"}', `{"type":"item.completed","item":{"id":"item_0","type":"error","message":"${errorMessage}"}}`, '{"type":"turn.completed"}'].join("\n"); + + const result = parseCodexLog(errorOnlyLog); + const resultEntry = result.logEntries.find(e => e.type === "session.result"); + + expect(resultEntry?.data?.errors).toEqual([errorMessage]); + expect(generateCopilotCliStyleSummary(result.logEntries)).toContain(errorMessage); + expect(result.markdown).toContain("Errors"); + expect(result.markdown).toContain(errorMessage); + }); }); });