diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 7cb26f3ca4..fd44623380 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -1738,11 +1738,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -1894,7 +1924,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index c87b69025e..8cce21c582 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -1734,11 +1734,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -1890,7 +1920,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-ai-inference-github-models.lock.yml b/pkg/cli/workflows/test-ai-inference-github-models.lock.yml index e8ef335120..5b073ba3f4 100644 --- a/pkg/cli/workflows/test-ai-inference-github-models.lock.yml +++ b/pkg/cli/workflows/test-ai-inference-github-models.lock.yml @@ -341,11 +341,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -497,7 +527,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-add-issue-comment.lock.yml b/pkg/cli/workflows/test-claude-add-issue-comment.lock.yml index f1f790bc2c..e0ff606b5d 100644 --- a/pkg/cli/workflows/test-claude-add-issue-comment.lock.yml +++ b/pkg/cli/workflows/test-claude-add-issue-comment.lock.yml @@ -338,11 +338,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -494,7 +524,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-add-issue-labels.lock.yml b/pkg/cli/workflows/test-claude-add-issue-labels.lock.yml index c411b52e4c..19dabb0f42 100644 --- a/pkg/cli/workflows/test-claude-add-issue-labels.lock.yml +++ b/pkg/cli/workflows/test-claude-add-issue-labels.lock.yml @@ -338,11 +338,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -494,7 +524,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-cache-memory.lock.yml b/pkg/cli/workflows/test-claude-cache-memory.lock.yml index 8d89b9b642..632427008e 100644 --- a/pkg/cli/workflows/test-claude-cache-memory.lock.yml +++ b/pkg/cli/workflows/test-claude-cache-memory.lock.yml @@ -403,11 +403,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -559,7 +589,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-command.lock.yml b/pkg/cli/workflows/test-claude-command.lock.yml index fc935a595b..d770468793 100644 --- a/pkg/cli/workflows/test-claude-command.lock.yml +++ b/pkg/cli/workflows/test-claude-command.lock.yml @@ -338,11 +338,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -494,7 +524,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-create-issue.lock.yml b/pkg/cli/workflows/test-claude-create-issue.lock.yml index cb4651287e..8bb4514eff 100644 --- a/pkg/cli/workflows/test-claude-create-issue.lock.yml +++ b/pkg/cli/workflows/test-claude-create-issue.lock.yml @@ -338,11 +338,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -494,7 +524,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-create-pull-request-review-comment.lock.yml b/pkg/cli/workflows/test-claude-create-pull-request-review-comment.lock.yml index 2796376dd4..fb07829d0d 100644 --- a/pkg/cli/workflows/test-claude-create-pull-request-review-comment.lock.yml +++ b/pkg/cli/workflows/test-claude-create-pull-request-review-comment.lock.yml @@ -338,11 +338,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -494,7 +524,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-create-pull-request.lock.yml b/pkg/cli/workflows/test-claude-create-pull-request.lock.yml index b5a26ed222..47acdab6e9 100644 --- a/pkg/cli/workflows/test-claude-create-pull-request.lock.yml +++ b/pkg/cli/workflows/test-claude-create-pull-request.lock.yml @@ -343,11 +343,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -499,7 +529,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-create-repository-security-advisory.lock.yml b/pkg/cli/workflows/test-claude-create-repository-security-advisory.lock.yml index 1b9c8058cf..9806b814f2 100644 --- a/pkg/cli/workflows/test-claude-create-repository-security-advisory.lock.yml +++ b/pkg/cli/workflows/test-claude-create-repository-security-advisory.lock.yml @@ -341,11 +341,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -497,7 +527,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-mcp.lock.yml b/pkg/cli/workflows/test-claude-mcp.lock.yml index 0330da402c..6da0a40ceb 100644 --- a/pkg/cli/workflows/test-claude-mcp.lock.yml +++ b/pkg/cli/workflows/test-claude-mcp.lock.yml @@ -341,11 +341,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -497,7 +527,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-missing-tool.lock.yml b/pkg/cli/workflows/test-claude-missing-tool.lock.yml index 747e1179a8..7c9cc88221 100644 --- a/pkg/cli/workflows/test-claude-missing-tool.lock.yml +++ b/pkg/cli/workflows/test-claude-missing-tool.lock.yml @@ -1699,11 +1699,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -1855,7 +1885,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-push-to-pr-branch.lock.yml b/pkg/cli/workflows/test-claude-push-to-pr-branch.lock.yml index 6424ffc097..4ca15b2736 100644 --- a/pkg/cli/workflows/test-claude-push-to-pr-branch.lock.yml +++ b/pkg/cli/workflows/test-claude-push-to-pr-branch.lock.yml @@ -343,11 +343,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -499,7 +529,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-claude-update-issue.lock.yml b/pkg/cli/workflows/test-claude-update-issue.lock.yml index 9f46ddf0c3..fd72e0a09f 100644 --- a/pkg/cli/workflows/test-claude-update-issue.lock.yml +++ b/pkg/cli/workflows/test-claude-update-issue.lock.yml @@ -341,11 +341,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -497,7 +527,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/cli/workflows/test-playwright-accessibility-contrast.lock.yml b/pkg/cli/workflows/test-playwright-accessibility-contrast.lock.yml index 9b5330fc07..77608dacec 100644 --- a/pkg/cli/workflows/test-playwright-accessibility-contrast.lock.yml +++ b/pkg/cli/workflows/test-playwright-accessibility-contrast.lock.yml @@ -1685,11 +1685,41 @@ jobs: */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -1841,7 +1871,7 @@ jobs: } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/workflow/claude_engine.go b/pkg/workflow/claude_engine.go index 4aded7ebfe..06ecb44cb5 100644 --- a/pkg/workflow/claude_engine.go +++ b/pkg/workflow/claude_engine.go @@ -817,17 +817,56 @@ func (e *ClaudeEngine) extractClaudeResultMetrics(line string) LogMetrics { return metrics } -// parseClaudeJSONLog parses Claude logs as a JSON array to find the result payload +// parseClaudeJSONLog parses Claude logs as a JSON array or mixed format (debug logs + JSONL) func (e *ClaudeEngine) parseClaudeJSONLog(logContent string, verbose bool) LogMetrics { var metrics LogMetrics - // Try to parse the entire log as a JSON array + // Try to parse the entire log as a JSON array first (old format) var logEntries []map[string]interface{} if err := json.Unmarshal([]byte(logContent), &logEntries); err != nil { + // If that fails, try to parse as mixed format (debug logs + JSONL) if verbose { - fmt.Printf("Failed to parse Claude log as JSON array: %v\n", err) + fmt.Printf("Failed to parse Claude log as JSON array, trying JSONL format: %v\n", err) + } + + logEntries = []map[string]interface{}{} + lines := strings.Split(logContent, "\n") + + for _, line := range lines { + trimmedLine := strings.TrimSpace(line) + if trimmedLine == "" { + continue // Skip empty lines + } + + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if !strings.HasPrefix(trimmedLine, "{") { + continue + } + + // Try to parse each line as JSON + var jsonEntry map[string]interface{} + if err := json.Unmarshal([]byte(trimmedLine), &jsonEntry); err != nil { + // Skip invalid JSON lines (could be partial debug output) + if verbose { + fmt.Printf("Skipping invalid JSON line: %s\n", trimmedLine[:min(50, len(trimmedLine))]) + } + continue + } + + logEntries = append(logEntries, jsonEntry) + } + + if len(logEntries) == 0 { + if verbose { + fmt.Printf("No valid JSON entries found in Claude log\n") + } + return metrics + } + + if verbose { + fmt.Printf("Extracted %d JSON entries from mixed format Claude log\n", len(logEntries)) } - return metrics } // Look for the result entry with type: "result" diff --git a/pkg/workflow/js/parse_claude_log.cjs b/pkg/workflow/js/parse_claude_log.cjs index cadbae70fb..97984a6a5f 100644 --- a/pkg/workflow/js/parse_claude_log.cjs +++ b/pkg/workflow/js/parse_claude_log.cjs @@ -38,11 +38,46 @@ function main() { */ function parseClaudeLog(logContent) { try { - const logEntries = JSON.parse(logContent); - if (!Array.isArray(logEntries)) { + let logEntries; + + // First, try to parse as JSON array (old format) + try { + logEntries = JSON.parse(logContent); + if (!Array.isArray(logEntries)) { + throw new Error("Not a JSON array"); + } + } catch (jsonArrayError) { + // If that fails, try to parse as mixed format (debug logs + JSONL) + logEntries = []; + const lines = logContent.split("\n"); + + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine === "") { + continue; // Skip empty lines + } + + // Skip debug log lines that don't start with { + // (these are typically timestamped debug messages) + if (!trimmedLine.startsWith("{")) { + continue; + } + + // Try to parse each line as JSON + try { + const jsonEntry = JSON.parse(trimmedLine); + logEntries.push(jsonEntry); + } catch (jsonLineError) { + // Skip invalid JSON lines (could be partial debug output) + continue; + } + } + } + + if (!Array.isArray(logEntries) || logEntries.length === 0) { return { markdown: - "## Agent Log Summary\n\nLog format not recognized as Claude JSON array.\n", + "## Agent Log Summary\n\nLog format not recognized as Claude JSON array or JSONL.\n", mcpFailures: [], }; } @@ -213,7 +248,7 @@ function parseClaudeLog(logContent) { } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { - markdown: `## Agent Log Summary\n\nError parsing Claude log: ${errorMessage}\n`, + markdown: `## Agent Log Summary\n\nError parsing Claude log (tried both JSON array and JSONL formats): ${errorMessage}\n`, mcpFailures: [], }; } diff --git a/pkg/workflow/log_parser_test.go b/pkg/workflow/log_parser_test.go index 8df3f968a0..d5d378289a 100644 --- a/pkg/workflow/log_parser_test.go +++ b/pkg/workflow/log_parser_test.go @@ -136,7 +136,7 @@ func TestParseClaudeLogSmoke(t *testing.T) { if err != nil { t.Fatalf("Failed to parse invalid Claude log: %v", err) } - if !strings.Contains(result, "Error parsing Claude log") { + if !strings.Contains(result, "Log format not recognized") { t.Error("Expected error message for invalid JSON in Claude log") } @@ -145,7 +145,7 @@ func TestParseClaudeLogSmoke(t *testing.T) { if err != nil { t.Fatalf("Failed to parse empty Claude log: %v", err) } - if !strings.Contains(result, "Error parsing Claude log") { + if !strings.Contains(result, "Log format not recognized") { t.Error("Expected error message for empty Claude log") } } @@ -217,3 +217,123 @@ func TestParseClaudeLogInitialization(t *testing.T) { t.Error("Expected Claude log output to contain Slash Commands section") } } + +// Test parsing Claude logs in mixed format (debug logs + JSONL) +func TestParseClaudeMixedFormatLog(t *testing.T) { + script := GetLogParserScript("parse_claude_log") + if script == "" { + t.Skip("parse_claude_log script not available") + } + + // Test with mixed format log (debug logs + JSONL entries) + mixedFormatLog := `2025-09-15T23:22:45.123Z [DEBUG] Initializing Claude Code CLI +2025-09-15T23:22:45.125Z [INFO] Session started +{"type":"system","subtype":"init","session_id":"test-123","tools":["Bash","Read"],"model":"claude-sonnet-4-20250514"} +2025-09-15T23:22:45.130Z [DEBUG] Processing user prompt +{"type":"assistant","message":{"content":[{"type":"text","text":"I'll help you with this task."},{"type":"tool_use","id":"tool_123","name":"Bash","input":{"command":"echo 'Hello World'"}}]}} +2025-09-15T23:22:45.135Z [DEBUG] Executing bash command +{"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"tool_123","content":"Hello World"}]}} +2025-09-15T23:22:45.140Z [INFO] Workflow completed successfully +{"type":"result","total_cost_usd":0.0015,"usage":{"input_tokens":150,"output_tokens":50},"num_turns":1,"duration_ms":2000} +2025-09-15T23:22:45.145Z [DEBUG] Cleanup completed` + + result, err := runJSLogParser(script, mixedFormatLog) + if err != nil { + t.Fatalf("Failed to parse mixed format Claude log: %v", err) + } + + // Verify essential sections are present + if !strings.Contains(result, "🚀 Initialization") { + t.Error("Expected mixed format Claude log output to contain Initialization section") + } + if !strings.Contains(result, "🤖 Commands and Tools") { + t.Error("Expected mixed format Claude log output to contain Commands and Tools section") + } + if !strings.Contains(result, "🤖 Reasoning") { + t.Error("Expected mixed format Claude log output to contain Reasoning section") + } + if !strings.Contains(result, "echo 'Hello World'") { + t.Error("Expected mixed format Claude log output to contain the bash command") + } + if !strings.Contains(result, "Total Cost") { + t.Error("Expected mixed format Claude log output to contain cost information") + } + if !strings.Contains(result, "test-123") { + t.Error("Expected mixed format Claude log output to contain session ID") + } + + // Test backward compatibility with pure JSON array format + jsonArrayLog := `[ + {"type":"system","subtype":"init","session_id":"test-456","tools":["Bash","Read"],"model":"claude-sonnet-4-20250514"}, + {"type":"assistant","message":{"content":[{"type":"text","text":"Working on it."},{"type":"tool_use","id":"tool_456","name":"Bash","input":{"command":"ls -la"}}]}}, + {"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"tool_456","content":"total 0"}]}}, + {"type":"result","total_cost_usd":0.002,"usage":{"input_tokens":100,"output_tokens":40},"num_turns":1} + ]` + + result, err = runJSLogParser(script, jsonArrayLog) + if err != nil { + t.Fatalf("Failed to parse JSON array Claude log: %v", err) + } + + // Verify backward compatibility works + if !strings.Contains(result, "🚀 Initialization") { + t.Error("Expected JSON array Claude log output to contain Initialization section") + } + if !strings.Contains(result, "ls -la") { + t.Error("Expected JSON array Claude log output to contain the bash command") + } + if !strings.Contains(result, "test-456") { + t.Error("Expected JSON array Claude log output to contain session ID") + } +} + +// Test Go Claude engine mixed format parsing +func TestClaudeEngineMixedFormatParsing(t *testing.T) { + engine := NewClaudeEngine() + + // Test with mixed format log (debug logs + JSONL entries) + mixedFormatLog := `2025-09-15T23:22:45.123Z [DEBUG] Initializing Claude Code CLI +2025-09-15T23:22:45.125Z [INFO] Session started +{"type":"system","subtype":"init","session_id":"test-123","tools":["Bash","Read"],"model":"claude-sonnet-4-20250514"} +2025-09-15T23:22:45.130Z [DEBUG] Processing user prompt +{"type":"assistant","message":{"content":[{"type":"text","text":"I'll help you with this task."},{"type":"tool_use","id":"tool_123","name":"Bash","input":{"command":"echo 'Hello World'"}}]}} +2025-09-15T23:22:45.135Z [DEBUG] Executing bash command +{"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"tool_123","content":"Hello World"}]}} +2025-09-15T23:22:45.140Z [INFO] Workflow completed successfully +{"type":"result","total_cost_usd":0.0015,"usage":{"input_tokens":150,"output_tokens":50},"num_turns":1,"duration_ms":2000} +2025-09-15T23:22:45.145Z [DEBUG] Cleanup completed` + + metrics := engine.ParseLogMetrics(mixedFormatLog, true) + + // Verify that metrics were extracted + if metrics.TokenUsage != 200 { + t.Errorf("Expected token usage 200 (150+50), got %d", metrics.TokenUsage) + } + if metrics.EstimatedCost != 0.0015 { + t.Errorf("Expected cost 0.0015, got %f", metrics.EstimatedCost) + } + if metrics.Turns != 1 { + t.Errorf("Expected 1 turn, got %d", metrics.Turns) + } + + // Test backward compatibility with pure JSON array format + jsonArrayLog := `[ + {"type":"system","subtype":"init","session_id":"test-456","tools":["Bash","Read"],"model":"claude-sonnet-4-20250514"}, + {"type":"assistant","message":{"content":[{"type":"text","text":"Working on it."},{"type":"tool_use","id":"tool_456","name":"Bash","input":{"command":"ls -la"}}]}}, + {"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"tool_456","content":"total 0"}]}}, + {"type":"result","total_cost_usd":0.002,"usage":{"input_tokens":100,"output_tokens":40},"num_turns":1} + ]` + + metrics = engine.ParseLogMetrics(jsonArrayLog, true) + + // Verify backward compatibility + if metrics.TokenUsage != 140 { + t.Errorf("Expected token usage 140 (100+40), got %d", metrics.TokenUsage) + } + if metrics.EstimatedCost != 0.002 { + t.Errorf("Expected cost 0.002, got %f", metrics.EstimatedCost) + } + if metrics.Turns != 1 { + t.Errorf("Expected 1 turn, got %d", metrics.Turns) + } +}