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
55 changes: 44 additions & 11 deletions actions/setup/js/log_parser_bootstrap.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ async function runLogParser(options) {
return null;
}

/**
* Count valid JSONL entries from a safe outputs file.
* @param {string} content - Raw safe outputs JSONL content
* @returns {number} Number of valid entries
*/
function countSafeOutputEntries(content) {
if (!content || content.trim().length === 0) {
return 0;
}

let count = 0;
const lines = content.trim().split(/\r?\n/);
for (const line of lines) {
const trimmedLine = line.trim();
if (!trimmedLine) {
continue;
}
try {
JSON.parse(trimmedLine);
count++;
} catch (e) {
// Ignore invalid JSONL lines
}
}
return count;
}

try {
const logPath = process.env.GH_AW_AGENT_OUTPUT;
if (!logPath) {
Expand Down Expand Up @@ -120,18 +147,20 @@ async function runLogParser(options) {
logEntries = result.logEntries || null;
}

if (markdown) {
// Read safe outputs file if available
let safeOutputsContent = "";
const safeOutputsPath = process.env.GH_AW_SAFE_OUTPUTS;
if (safeOutputsPath && fs.existsSync(safeOutputsPath)) {
try {
safeOutputsContent = fs.readFileSync(safeOutputsPath, "utf8");
} catch (error) {
core.warning(`Failed to read safe outputs file: ${getErrorMessage(error)}`);
}
// Read safe outputs file if available
let safeOutputsContent = "";
let safeOutputEntriesCount = 0;
const safeOutputsPath = process.env.GH_AW_SAFE_OUTPUTS;
if (safeOutputsPath && fs.existsSync(safeOutputsPath)) {
try {
safeOutputsContent = fs.readFileSync(safeOutputsPath, "utf8");
safeOutputEntriesCount = countSafeOutputEntries(safeOutputsContent);
} catch (error) {
core.warning(`Failed to read safe outputs file: ${getErrorMessage(error)}`);
}
}

if (markdown) {
// Generate lightweight plain text summary for core.info and Copilot CLI style for step summary
if (logEntries && Array.isArray(logEntries) && logEntries.length > 0) {
// Extract model from init entry if available
Expand Down Expand Up @@ -215,7 +244,11 @@ async function runLogParser(options) {
// Handle MCP server failures if present
if (mcpFailures && mcpFailures.length > 0) {
const failedServers = mcpFailures.join(", ");
core.setFailed(`${ERR_API}: MCP server(s) failed to launch: ${failedServers}`);
if (safeOutputEntriesCount > 0) {
core.warning(`MCP server(s) failed to launch (${failedServers}), but agent completed with ${safeOutputEntriesCount} safe output ${safeOutputEntriesCount === 1 ? "entry" : "entries"}`);
} else {
core.setFailed(`${ERR_API}: MCP server(s) failed to launch: ${failedServers}`);
}
}

// Handle max-turns limit if hit
Expand Down
19 changes: 19 additions & 0 deletions actions/setup/js/log_parser_bootstrap.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,25 @@ describe("log_parser_bootstrap.cjs", () => {
const mockParseLog = vi.fn().mockReturnValue({ markdown: "## Result\n", mcpFailures: ["server1", "server2"], maxTurnsHit: !1 });
(runLogParser({ parseLog: mockParseLog, parserName: "TestParser" }), expect(mockCore.setFailed).toHaveBeenCalledWith(`${ERR_API}: MCP server(s) failed to launch: server1, server2`), fs.unlinkSync(logFile), fs.rmdirSync(tmpDir));
}),
it("should warn instead of failing MCP failures when safe outputs exist", () => {
const tmpDir = fs.mkdtempSync(path.join(__dirname, "test-"));
const logFile = path.join(tmpDir, "test.log");
const safeOutputsFile = path.join(tmpDir, "safe-outputs.jsonl");

fs.writeFileSync(logFile, "content");
fs.writeFileSync(safeOutputsFile, ` ${JSON.stringify({ type: "create_issue", title: "Test", body: "Test body" })}\r\n`);
process.env.GH_AW_AGENT_OUTPUT = logFile;
process.env.GH_AW_SAFE_OUTPUTS = safeOutputsFile;

const mockParseLog = vi.fn().mockReturnValue({ markdown: "## Result\n", mcpFailures: ["server1"], maxTurnsHit: !1 });

(runLogParser({ parseLog: mockParseLog, parserName: "TestParser" }),
expect(mockCore.warning).toHaveBeenCalledWith("MCP server(s) failed to launch (server1), but agent completed with 1 safe output entry"),
expect(mockCore.setFailed).not.toHaveBeenCalled(),
fs.unlinkSync(logFile),
fs.unlinkSync(safeOutputsFile),
fs.rmdirSync(tmpDir));
}),
it("should handle max-turns limit reached", () => {
const tmpDir = fs.mkdtempSync(path.join(__dirname, "test-")),
logFile = path.join(tmpDir, "test.log");
Expand Down