From dd98153d174be52cf9623e473ad500878b41fdac Mon Sep 17 00:00:00 2001 From: Alison Shao <54658187+alisonshao@users.noreply.github.com> Date: Wed, 13 May 2026 17:05:38 -0700 Subject: [PATCH] chore(ci_monitor): drop post_bisect_to_slack (#25192) --- scripts/ci_monitor/ci_auto_bisect.py | 8 +- scripts/ci_monitor/post_bisect_to_slack.py | 253 --------------------- 2 files changed, 4 insertions(+), 257 deletions(-) delete mode 100755 scripts/ci_monitor/post_bisect_to_slack.py diff --git a/scripts/ci_monitor/ci_auto_bisect.py b/scripts/ci_monitor/ci_auto_bisect.py index f4461ad23..2600d343f 100755 --- a/scripts/ci_monitor/ci_auto_bisect.py +++ b/scripts/ci_monitor/ci_auto_bisect.py @@ -742,8 +742,8 @@ class SkillLoadError(Exception): # Required sections to extract from SKILL.md. If any are missing after a -# rename or restructuring, the script raises SkillLoadError so the team -# gets a Slack notification instead of silently falling back. +# rename or restructuring, the script raises SkillLoadError instead of +# silently falling back. _REQUIRED_SKILL_SECTIONS = { "Key Patterns to Recognize": r"## Key Patterns to Recognize\n(.*?)(?=\n## |\Z)", "Important Notes": r"## Important Notes\n(.*?)(?=\n## |\Z)", @@ -1280,8 +1280,8 @@ def main(): traceback.print_exc() - # Write an error result file so the Slack notification step can - # report the failure instead of silently skipping + # Write an error result file so downstream consumers can report + # the failure instead of silently skipping if args.output: error_output = { "analysis_timestamp": datetime.now().isoformat(), diff --git a/scripts/ci_monitor/post_bisect_to_slack.py b/scripts/ci_monitor/post_bisect_to_slack.py deleted file mode 100755 index 06037896b..000000000 --- a/scripts/ci_monitor/post_bisect_to_slack.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python3 -""" -Post CI auto-bisect results to Slack. - -Reads the bisect_results.json produced by ci_auto_bisect.py and posts -a summary message with threaded details to the CI failures Slack channel. -""" - -import argparse -import json -import logging -import os -import sys -from datetime import datetime - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -# CI failures channel (same as post_ci_failures_to_slack.py) -SLACK_CHANNEL_ID = "C0A2DG0R7CJ" - -CLASSIFICATION_EMOJI = { - "code_regression": "🔴", - "hardware_issue": "🟠", - "environment_change": "🟡", - "flaky_test": "🔵", - "unknown": "⚪", -} - -CLASSIFICATION_LABELS = { - "code_regression": "Code Regression", - "hardware_issue": "Hardware Issue", - "environment_change": "Environment Change", - "flaky_test": "Flaky Test", - "unknown": "Unknown", -} - - -def post_bisect_to_slack(report_file: str) -> bool: - """Post bisect results to Slack with threaded details.""" - from slack_sdk import WebClient - - token = os.environ.get("SGLANG_DIFFUSION_SLACK_TOKEN") - if not token: - logger.info("Slack post skipped: no token") - return False - - with open(report_file) as f: - report = json.load(f) - - try: - results = report.get("results", []) - summary = report.get("summary", {}) - total_analyzed = report.get("total_failures_analyzed", 0) - total_tokens = report.get("total_tokens_used", 0) - error_msg = report.get("error") - - client = WebClient(token=token) - run_id = os.environ.get("GITHUB_RUN_ID", "") - workflow_url = "" - if run_id: - workflow_url = ( - f"https://github.com/sgl-project/sglang/actions/runs/{run_id}" - ) - - # Build summary message - if error_msg: - mentions = "<@U09R55D8EAY> <@U09ABMCKQPM>" - summary_text = ( - f"{mentions} 🚨 *CI Auto Bisect Failed*\n" f"Error: `{error_msg[:200]}`" - ) - if workflow_url: - summary_text += f"\n<{workflow_url}|View logs>" - color = "danger" - elif total_analyzed == 0: - summary_text = "✅ *CI Auto Bisect*: No failures requiring analysis" - if workflow_url: - summary_text += f"\n<{workflow_url}|View run>" - color = "good" - else: - mentions = "<@U09R55D8EAY> <@U09ABMCKQPM>" - lines = [f"{mentions} 🔍 *CI Auto Bisect Results*"] - lines.append(f"Analyzed {total_analyzed} failures:\n") - - # Order: regressions first (most actionable) - class_order = [ - "code_regressions", - "hardware_issues", - "environment_changes", - "flaky_tests", - "unknown", - ] - class_to_key = { - "code_regressions": "code_regression", - "hardware_issues": "hardware_issue", - "environment_changes": "environment_change", - "flaky_tests": "flaky_test", - "unknown": "unknown", - } - - for cls_key in class_order: - count = summary.get(cls_key, 0) - if count > 0: - cls = class_to_key[cls_key] - emoji = CLASSIFICATION_EMOJI.get(cls, "⚪") - label = CLASSIFICATION_LABELS.get(cls, cls) - lines.append(f" {emoji} *{label}*: {count}") - - if workflow_url: - lines.append(f"\n<{workflow_url}|View full bisect report>") - - summary_text = "\n".join(lines) - - # Color based on most severe classification - if summary.get("code_regressions", 0) > 0: - color = "danger" - elif ( - summary.get("hardware_issues", 0) > 0 - or summary.get("environment_changes", 0) > 0 - ): - color = "warning" - else: - color = "#439FE0" # Blue for flaky only - - # Post parent message - response = client.chat_postMessage( - channel=SLACK_CHANNEL_ID, - text=summary_text, - attachments=[ - { - "color": color, - "footer": f"SGLang CI Auto Bisect | {total_tokens} tokens used", - "footer_icon": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png", - "ts": int(datetime.now().timestamp()), - } - ], - ) - - thread_ts = response.get("ts") - if not thread_ts: - logger.warning("Slack response missing 'ts', cannot post thread") - return True - - # Post detailed breakdown in thread if there are results - if results: - detail_lines = ["*Detailed Bisect Results*\n"] - - # Group by classification for organized display - by_class = {} - for r in results: - cls = r.get("classification", "unknown") - by_class.setdefault(cls, []).append(r) - - class_display_order = [ - "code_regression", - "hardware_issue", - "environment_change", - "flaky_test", - "unknown", - ] - - for cls in class_display_order: - cls_results = by_class.get(cls, []) - if not cls_results: - continue - - emoji = CLASSIFICATION_EMOJI.get(cls, "⚪") - label = CLASSIFICATION_LABELS.get(cls, cls) - detail_lines.append(f"\n*━━━ {emoji} {label} ━━━*\n") - - for r in cls_results: - target = r.get("target", {}) - test_file = target.get("test_file", "unknown") - job_name = target.get("job_name", "unknown") - confidence = r.get("confidence", "unknown") - evidence = r.get("evidence_summary", "N/A") - fix = r.get("recommended_fix", "N/A") - suspected = r.get("suspected_commit") - suspected_pr = r.get("suspected_pr") - job_url = target.get("last_failure_job_url", "") - streak = target.get("current_streak", 0) - - detail_lines.append(f"• *`{test_file}`* in `{job_name}`") - detail_lines.append( - f" Streak: {streak} | Confidence: {confidence}" - ) - - if suspected: - cause_str = f"`{suspected}`" - if suspected_pr: - cause_str += f" (PR #{suspected_pr})" - detail_lines.append(f" Suspected: {cause_str}") - - detail_lines.append(f" Evidence: {evidence}") - detail_lines.append(f" Fix: {fix}") - - if job_url: - detail_lines.append(f" <{job_url}|View failing job>") - detail_lines.append("") - - detail_text = "\n".join(detail_lines) - - # Slack has a 4000 char limit per message; split if needed - if len(detail_text) > 3900: - chunks = [] - current_chunk = "" - for line in detail_lines: - if len(current_chunk) + len(line) + 1 > 3900: - chunks.append(current_chunk) - current_chunk = line - else: - current_chunk += "\n" + line if current_chunk else line - if current_chunk: - chunks.append(current_chunk) - - for chunk in chunks: - client.chat_postMessage( - channel=SLACK_CHANNEL_ID, - thread_ts=thread_ts, - text=chunk, - ) - else: - client.chat_postMessage( - channel=SLACK_CHANNEL_ID, - thread_ts=thread_ts, - text=detail_text, - ) - - logger.info("Bisect results posted to Slack successfully") - return True - - except Exception: - logger.exception("Failed to post bisect results to Slack") - return False - - -def main(): - parser = argparse.ArgumentParser(description="Post CI auto-bisect results to Slack") - parser.add_argument( - "--report-file", - type=str, - required=True, - help="Path to bisect_results.json", - ) - - args = parser.parse_args() - - success = post_bisect_to_slack(args.report_file) - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main()