Debug GitHub Actions failures efficiently.
| 32 | |
| 33 | |
| 34 | class GitHubDebugger: |
| 35 | """Debug GitHub Actions failures efficiently.""" |
| 36 | |
| 37 | # Critical error patterns - actual compilation failures |
| 38 | CRITICAL_PATTERNS = [ |
| 39 | r"\.(cpp|h|c|ino):\d+:\d+: error:", # Compiler errors with file:line:col |
| 40 | r"static assertion failed", |
| 41 | r"compilation terminated", |
| 42 | r"undefined reference", |
| 43 | r"fatal error:", |
| 44 | ] |
| 45 | |
| 46 | # General error patterns |
| 47 | ERROR_PATTERNS = [ |
| 48 | r"error:", |
| 49 | r"Error compiling", |
| 50 | r"FAILED", |
| 51 | r"Assertion.*failed", |
| 52 | r"undefined reference", |
| 53 | r"fatal error:", |
| 54 | r"compilation terminated", |
| 55 | r"Test.*failed", |
| 56 | r"Error \d+", |
| 57 | r"^\s*\^\s*$", # Compiler error markers (^) |
| 58 | ] |
| 59 | |
| 60 | # Patterns to exclude from critical error count (tool warnings, not build failures) |
| 61 | EXCLUDE_PATTERNS = [ |
| 62 | r"esp_idf_size: error: unrecognized arguments", |
| 63 | r"Warning: esp-idf-size exited with code", |
| 64 | ] |
| 65 | |
| 66 | def __init__(self, run_id: str, max_errors: int = 10, context_lines: int = 5): |
| 67 | """Initialize debugger. |
| 68 | |
| 69 | Args: |
| 70 | run_id: GitHub run ID or URL |
| 71 | max_errors: Maximum number of errors to collect before stopping |
| 72 | context_lines: Lines of context before/after errors |
| 73 | """ |
| 74 | self.run_id = self._extract_run_id(run_id) |
| 75 | self.max_errors = max_errors |
| 76 | self.context_lines = context_lines |
| 77 | self.repo = self._get_repo() |
| 78 | self.critical_errors: list[dict[str, str]] = [] |
| 79 | self.warnings: list[dict[str, str]] = [] |
| 80 | self.log_file: Optional[Path] = None |
| 81 | |
| 82 | def _extract_run_id(self, run_input: str) -> str: |
| 83 | """Extract run ID from URL or return as-is if already an ID.""" |
| 84 | # Check if it's a URL |
| 85 | match = re.search(r"/actions/runs/(\d+)", run_input) |
| 86 | if match: |
| 87 | return match.group(1) |
| 88 | # Assume it's already an ID |
| 89 | return run_input |
| 90 | |
| 91 | def _get_repo(self) -> str: |