feat: smart error extraction for failed workflow logs
All checks were successful
Build and Push Gitea MCP Docker Image / build (push) Successful in 1m25s

For failed jobs, instead of just showing the tail (which is cleanup),
now scans for error patterns (, Error:, FAILED, TypeScript errors,
npm/pnpm errors, exit codes, etc.) and shows context around them.

Falls back to tail for successful jobs or if no errors detected.
This commit is contained in:
Ben
2025-12-22 14:50:03 +00:00
parent 7c4b35dc68
commit 77a945bd1e

View File

@@ -400,15 +400,71 @@ async def get_workflow_run_logs(
elif isinstance(logs_result, str):
log_content = logs_result
# Apply tail if requested
if tail_lines > 0 and log_content:
lines = log_content.splitlines()
total_lines = len(lines)
if total_lines > tail_lines:
lines = log_content.splitlines()
total_lines = len(lines)
# For failed jobs, try to find error context instead of just tail
if run_status == "failure" and tail_lines > 0 and total_lines > tail_lines:
# Error indicators to search for
error_patterns = [
"", "Error:", "ERROR:", "error:", "FAILED", "Failed",
"fatal:", "FATAL:", "Exception:", "exception:",
"Cannot find", "not found", "No such file",
"Permission denied", "command not found",
"npm ERR!", "pnpm ERR!", "yarn error",
"TypeScript error", "TS2", "TS1", # TypeScript errors
"SyntaxError", "ReferenceError", "TypeError",
"Build failed", "build failed", "Compilation failed",
"exit code 1", "exit code 2", "exited with",
]
# Find lines containing errors
error_line_indices = []
for i, line in enumerate(lines):
for pattern in error_patterns:
if pattern in line:
error_line_indices.append(i)
break
if error_line_indices:
# Get unique error regions with context (5 lines before, 3 after)
context_before = 5
context_after = 3
selected_lines = set()
for idx in error_line_indices:
start = max(0, idx - context_before)
end = min(total_lines, idx + context_after + 1)
for i in range(start, end):
selected_lines.add(i)
# Cap at tail_lines to avoid overwhelming output
sorted_indices = sorted(selected_lines)
if len(sorted_indices) > tail_lines:
# Take the last error regions
sorted_indices = sorted_indices[-tail_lines:]
# Build output with line breaks between non-contiguous sections
output_parts = []
prev_idx = -2
for idx in sorted_indices:
if idx > prev_idx + 1:
if output_parts:
output_parts.append("...")
output_parts.append(lines[idx])
prev_idx = idx
log_content = f"... (showing {len(sorted_indices)} error-relevant lines of {total_lines} total) ...\n" + "\n".join(output_parts)
else:
# No errors found, fall back to tail
lines = lines[-tail_lines:]
log_content = f"... (showing last {tail_lines} of {total_lines} lines) ...\n" + "\n".join(lines)
else:
log_content = "\n".join(lines)
elif tail_lines > 0 and total_lines > tail_lines:
# Not a failure or no smart extraction, just tail
lines = lines[-tail_lines:]
log_content = f"... (showing last {tail_lines} of {total_lines} lines) ...\n" + "\n".join(lines)
else:
log_content = "\n".join(lines)
return json.dumps({
"run_number": run_number,