feat: smart error extraction for failed workflow logs
All checks were successful
Build and Push Gitea MCP Docker Image / build (push) Successful in 1m25s
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:
70
server.py
70
server.py
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user