From 20ed4ce20f1df0a93f4734c18edaa37f1e03c83a Mon Sep 17 00:00:00 2001 From: BHARATH0153 Date: Sat, 6 Jun 2026 08:15:46 +0530 Subject: [PATCH 1/5] [fix] Issue #694: CI failure bot stays silent when no failures detected Two-layer guard: skip analysis in analyze_failure.py when logs contain no error markers, and skip the Post Comment step in reusable-bot-ci-failure.yml when no errors are found. Closes #694 --- .../actions/bot-ci-failure/analyze_failure.py | 20 +++++++++++++++++++ .github/workflows/reusable-bot-ci-failure.yml | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/.github/actions/bot-ci-failure/analyze_failure.py b/.github/actions/bot-ci-failure/analyze_failure.py index dc0e537d..eede0c33 100644 --- a/.github/actions/bot-ci-failure/analyze_failure.py +++ b/.github/actions/bot-ci-failure/analyze_failure.py @@ -391,6 +391,26 @@ def main(): ): print("::warning::Skipping: No failure logs to analyse.", file=sys.stderr) return + if not error_log.strip(): + print("::warning::Skipping: Empty failure logs.", file=sys.stderr) + return + if not tests_failed and not transient_only: + try: + with open("failed_logs.txt") as f: + raw = f.read() + no_failure_patterns = [ + "No failed jobs found", + "Failed jobs found but logs unavailable", + "Could not fetch failed jobs", + ] + if any(p in raw for p in no_failure_patterns): + print( + "::notice::No CI failures detected; skipping analysis.", + file=sys.stderr, + ) + return + except OSError: + pass # Only fetch the full repository code context when automated tests # actually failed. For QA-only or commit-message failures the code # is not needed and would waste prompt tokens. diff --git a/.github/workflows/reusable-bot-ci-failure.yml b/.github/workflows/reusable-bot-ci-failure.yml index c17dfcc3..d2334b43 100644 --- a/.github/workflows/reusable-bot-ci-failure.yml +++ b/.github/workflows/reusable-bot-ci-failure.yml @@ -183,4 +183,8 @@ jobs: echo "AI analysis produced no output; skipping comment." exit 0 fi + if ! grep -q "##\[error\]" failed_logs.txt 2>/dev/null; then + echo "No CI failures detected; skipping comment." + exit 0 + fi gh pr comment "$PR_NUM" --repo "$REPO" --body-file solution.md From 4c80968654120d9b1744893c7d6c3a8b923c8061 Mon Sep 17 00:00:00 2001 From: BHARATH0153 Date: Sat, 6 Jun 2026 22:55:54 +0530 Subject: [PATCH 2/5] remove ai comments --- .github/actions/bot-ci-failure/analyze_failure.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/actions/bot-ci-failure/analyze_failure.py b/.github/actions/bot-ci-failure/analyze_failure.py index eede0c33..8194a604 100644 --- a/.github/actions/bot-ci-failure/analyze_failure.py +++ b/.github/actions/bot-ci-failure/analyze_failure.py @@ -68,17 +68,11 @@ def _normalize_for_dedup(text): def _is_transient_failure(body): - """Return True if the log body looks like a transient/infra failure.""" body_lower = body.lower() return any(marker.lower() in body_lower for marker in TRANSIENT_FAILURE_MARKERS) def _extract_failed_tests(body): - """Return only the failing test sections from a test-runner log. - - Keeps every block that sits between two separator lines (====…) - and contains at least one failure marker. - """ # Split on the "======…" separators that unittest / pytest emit. blocks = re.split(r"(?:={50,})", body) failed = [ @@ -95,12 +89,6 @@ def _extract_failed_tests(body): def _strip_slow_test_output(text): - """Remove the openwisp-utils slow-test report from log output. - - The TimeLoggingTestRunner prints a summary of tests that exceeded a - time threshold. This is purely informational and must not be fed to - the LLM, which tends to misinterpret the count as test failures. - """ # Strip the full block: header, individual slow-test lines, and total. # The header varies (e.g. "Summary of slow tests (>0.3s)" or # "Slow tests (threshold 2.00s)") so we match generically. From fb075a3b589249aa5f7658fb8767db80afb9052a Mon Sep 17 00:00:00 2001 From: BHARATH0153 Date: Sat, 6 Jun 2026 22:58:41 +0530 Subject: [PATCH 3/5] Revert "remove ai comments" This reverts commit 4c80968654120d9b1744893c7d6c3a8b923c8061. --- .github/actions/bot-ci-failure/analyze_failure.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/actions/bot-ci-failure/analyze_failure.py b/.github/actions/bot-ci-failure/analyze_failure.py index 8194a604..eede0c33 100644 --- a/.github/actions/bot-ci-failure/analyze_failure.py +++ b/.github/actions/bot-ci-failure/analyze_failure.py @@ -68,11 +68,17 @@ def _normalize_for_dedup(text): def _is_transient_failure(body): + """Return True if the log body looks like a transient/infra failure.""" body_lower = body.lower() return any(marker.lower() in body_lower for marker in TRANSIENT_FAILURE_MARKERS) def _extract_failed_tests(body): + """Return only the failing test sections from a test-runner log. + + Keeps every block that sits between two separator lines (====…) + and contains at least one failure marker. + """ # Split on the "======…" separators that unittest / pytest emit. blocks = re.split(r"(?:={50,})", body) failed = [ @@ -89,6 +95,12 @@ def _extract_failed_tests(body): def _strip_slow_test_output(text): + """Remove the openwisp-utils slow-test report from log output. + + The TimeLoggingTestRunner prints a summary of tests that exceeded a + time threshold. This is purely informational and must not be fed to + the LLM, which tends to misinterpret the count as test failures. + """ # Strip the full block: header, individual slow-test lines, and total. # The header varies (e.g. "Summary of slow tests (>0.3s)" or # "Slow tests (threshold 2.00s)") so we match generically. From beea9420fb1c83448fc762b8a4d55437d9cb3c1c Mon Sep 17 00:00:00 2001 From: BHARATH0153 Date: Sat, 6 Jun 2026 23:03:26 +0530 Subject: [PATCH 4/5] [change] Trigger CI rerun From f60747329e1d6cf3f4080122c292039cc0f54bb5 Mon Sep 17 00:00:00 2001 From: BHARATH0153 Date: Tue, 9 Jun 2026 11:12:11 +0530 Subject: [PATCH 5/5] [fix] Check error_log directly instead of re-reading failed_logs.txt --- .../actions/bot-ci-failure/analyze_failure.py | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/.github/actions/bot-ci-failure/analyze_failure.py b/.github/actions/bot-ci-failure/analyze_failure.py index eede0c33..648e5316 100644 --- a/.github/actions/bot-ci-failure/analyze_failure.py +++ b/.github/actions/bot-ci-failure/analyze_failure.py @@ -395,22 +395,17 @@ def main(): print("::warning::Skipping: Empty failure logs.", file=sys.stderr) return if not tests_failed and not transient_only: - try: - with open("failed_logs.txt") as f: - raw = f.read() - no_failure_patterns = [ - "No failed jobs found", - "Failed jobs found but logs unavailable", - "Could not fetch failed jobs", - ] - if any(p in raw for p in no_failure_patterns): - print( - "::notice::No CI failures detected; skipping analysis.", - file=sys.stderr, - ) - return - except OSError: - pass + no_failure_patterns = [ + "No failed jobs found", + "Failed jobs found but logs unavailable", + "Could not fetch failed jobs", + ] + if any(p in error_log for p in no_failure_patterns): + print( + "::notice::No CI failures detected; skipping analysis.", + file=sys.stderr, + ) + return # Only fetch the full repository code context when automated tests # actually failed. For QA-only or commit-message failures the code # is not needed and would waste prompt tokens.