Skip to content
Closed
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
38 changes: 32 additions & 6 deletions actions/setup/js/parse_copilot_log.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function extractPremiumRequestCount(logContent) {
*/
function parseCopilotLog(logContent) {
let logEntries;
let mcpFailures = [];

// First, try to parse as JSON array (structured format)
try {
Expand All @@ -49,9 +50,10 @@ function parseCopilotLog(logContent) {
}
} catch (jsonArrayError) {
// If that fails, try to parse as debug logs format
const debugLogEntries = parseDebugLogFormat(logContent);
if (debugLogEntries && debugLogEntries.length > 0) {
logEntries = debugLogEntries;
const debugLogResult = parseDebugLogFormat(logContent);
if (debugLogResult && debugLogResult.entries && debugLogResult.entries.length > 0) {
logEntries = debugLogResult.entries;
mcpFailures = debugLogResult.mcpFailures || [];
} else {
// Try JSONL format using shared function
logEntries = parseLogEntries(logContent);
Expand Down Expand Up @@ -126,7 +128,12 @@ function parseCopilotLog(logContent) {
},
});

return { markdown, logEntries };
// Return result with mcpFailures if any were detected
const result = { markdown, logEntries };
if (mcpFailures.length > 0) {
result.mcpFailures = mcpFailures;
}
return result;
}

/**
Expand Down Expand Up @@ -208,15 +215,34 @@ function scanForToolErrors(logContent) {
/**
* Parses Copilot CLI debug log format and reconstructs the conversation flow
* @param {string} logContent - Raw debug log content
* @returns {Array} Array of log entries in structured format
* @returns {{entries: Array, mcpFailures: string[]}} Object with array of log entries and array of failed MCP server names
*/
function parseDebugLogFormat(logContent) {
const entries = [];
const lines = logContent.split("\n");
const mcpFailures = [];

// First pass: scan for tool errors
const toolErrors = scanForToolErrors(logContent);

// Scan for MCP server connection failures
// Pattern: [ERROR] Failed to start MCP client for remote server <server-name>: TypeError: fetch failed
for (const line of lines) {
const mcpFailureMatch = line.match(/\[ERROR\]\s+Failed to start MCP client for remote server\s+([^:]+):\s+TypeError:\s+fetch failed/i);
if (mcpFailureMatch) {
const serverName = mcpFailureMatch[1].trim();
if (serverName && !mcpFailures.includes(serverName)) {
mcpFailures.push(serverName);
}
}
}

// Fail the step immediately if MCP server failures are detected
if (mcpFailures.length > 0) {
const failedServers = mcpFailures.join(", ");
core.setFailed(`MCP server(s) failed to launch: ${failedServers}`);
}

// Extract model information from the start
let model = "unknown";
let sessionId = null;
Expand Down Expand Up @@ -683,7 +709,7 @@ function parseDebugLogFormat(logContent) {
}
}

return entries;
return { entries, mcpFailures };
}

// Export for testing
Expand Down
79 changes: 79 additions & 0 deletions actions/setup/js/parse_copilot_log.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -278,4 +278,83 @@ describe("parse_copilot_log.cjs", () => {
expect(result.markdown).toContain("safe_outputs::create_issue");
});
});

describe("MCP server failure detection", () => {
it("should detect MCP server connection failures from debug logs", () => {
const logWithMcpFailure = `2026-01-11T07:21:35.050Z [DEBUG] Starting Copilot CLI
2026-01-11T07:21:35.050Z [ERROR] Failed to start MCP client for remote server github: TypeError: fetch failed
2026-01-11T07:21:35.100Z [DEBUG] data:
2026-01-11T07:21:35.100Z [DEBUG] {
2026-01-11T07:21:35.100Z [DEBUG] "choices": [{"message": {"content": "test"}}]
2026-01-11T07:21:35.100Z [DEBUG] }`;

const result = parseCopilotLog(logWithMcpFailure);

expect(result.mcpFailures).toBeDefined();
expect(result.mcpFailures).toHaveLength(1);
expect(result.mcpFailures[0]).toBe("github");
expect(mockCore.setFailed).toHaveBeenCalledWith("MCP server(s) failed to launch: github");
});

it("should detect multiple MCP server failures", () => {
const logWithMultipleMcpFailures = `2026-01-11T07:21:35.050Z [DEBUG] Starting Copilot CLI
2026-01-11T07:21:35.050Z [ERROR] Failed to start MCP client for remote server github: TypeError: fetch failed
2026-01-11T07:21:35.100Z [ERROR] Failed to start MCP client for remote server playwright: TypeError: fetch failed
2026-01-11T07:21:35.150Z [DEBUG] data:
2026-01-11T07:21:35.150Z [DEBUG] {
2026-01-11T07:21:35.150Z [DEBUG] "choices": [{"message": {"content": "test"}}]
2026-01-11T07:21:35.150Z [DEBUG] }`;

const result = parseCopilotLog(logWithMultipleMcpFailures);

expect(result.mcpFailures).toBeDefined();
expect(result.mcpFailures).toHaveLength(2);
expect(result.mcpFailures).toContain("github");
expect(result.mcpFailures).toContain("playwright");
expect(mockCore.setFailed).toHaveBeenCalledWith("MCP server(s) failed to launch: github, playwright");
});

it("should not report MCP failures for successful logs", () => {
const successfulLog = `2026-01-11T07:21:35.050Z [DEBUG] Starting Copilot CLI
2026-01-11T07:21:35.050Z [DEBUG] Connected to MCP server github
2026-01-11T07:21:35.100Z [DEBUG] data:
2026-01-11T07:21:35.100Z [DEBUG] {
2026-01-11T07:21:35.100Z [DEBUG] "choices": [{"message": {"content": "test"}}]
2026-01-11T07:21:35.100Z [DEBUG] }`;

const result = parseCopilotLog(successfulLog);

expect(result.mcpFailures).toBeUndefined();
expect(mockCore.setFailed).not.toHaveBeenCalled();
});

it("should handle JSON format without MCP failures", () => {
const jsonLog = JSON.stringify([
{ type: "system", subtype: "init", session_id: "test", tools: ["Bash"], model: "gpt-4" },
{ type: "result", num_turns: 1, usage: { input_tokens: 100, output_tokens: 50 } },
]);

const result = parseCopilotLog(jsonLog);

expect(result.mcpFailures).toBeUndefined();
expect(mockCore.setFailed).not.toHaveBeenCalled();
});

it("should avoid duplicate MCP server names in failures list", () => {
const logWithDuplicateErrors = `2026-01-11T07:21:35.050Z [DEBUG] Starting Copilot CLI
2026-01-11T07:21:35.050Z [ERROR] Failed to start MCP client for remote server github: TypeError: fetch failed
2026-01-11T07:21:35.100Z [ERROR] Failed to start MCP client for remote server github: TypeError: fetch failed
2026-01-11T07:21:35.150Z [DEBUG] data:
2026-01-11T07:21:35.150Z [DEBUG] {
2026-01-11T07:21:35.150Z [DEBUG] "choices": [{"message": {"content": "test"}}]
2026-01-11T07:21:35.150Z [DEBUG] }`;

const result = parseCopilotLog(logWithDuplicateErrors);

expect(result.mcpFailures).toBeDefined();
expect(result.mcpFailures).toHaveLength(1);
expect(result.mcpFailures[0]).toBe("github");
expect(mockCore.setFailed).toHaveBeenCalledWith("MCP server(s) failed to launch: github");
});
});
});
Loading