Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions actions/setup/js/log_parser_format.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
40 changes: 31 additions & 9 deletions actions/setup/js/parse_codex_log.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Comment on lines +412 to +416

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a no-usage item.completed error regression test asserting session.result.data.errors and the Copilot CLI-style rendered summary. The downstream formatter now includes result errors in Statistics.

}
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 = "<details>\n<summary>Reasoning</summary>\n\n";
let markdown = "";
if (errorMessages.length > 0) {
markdown += "<details>\n<summary>Errors</summary>\n\n";
for (const message of errorMessages) {
markdown += `> ${message}\n\n`;
}
markdown += "</details>\n\n";
}
markdown += "<details>\n<summary>Reasoning</summary>\n\n";
for (const item of parsedData) {
if (item.type === "text") {
markdown += `${item.content}\n\n`;
Expand Down Expand Up @@ -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,
});
}

Expand Down
15 changes: 15 additions & 0 deletions actions/setup/js/parse_codex_log.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe("parse_codex_log.cjs", () => {
let estimateTokens;
let formatDuration;
let extractMCPInitialization;
let generateCopilotCliStyleSummary;

beforeEach(async () => {
// Mock core actions methods
Expand Down Expand Up @@ -38,6 +39,7 @@ describe("parse_codex_log.cjs", () => {
truncateString = sharedModule.truncateString;
estimateTokens = sharedModule.estimateTokens;
formatDuration = sharedModule.formatDuration;
generateCopilotCliStyleSummary = sharedModule.generateCopilotCliStyleSummary;
});

describe("parseCodexLog function", () => {
Expand Down Expand Up @@ -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("<summary>Errors</summary>");
expect(result.markdown).toContain(errorMessage);
});
});
});
Loading