99
1010FLAKE_MARKER = "<!-- CI3_FLAKE_DETECTION_COMMENT -->"
1111TEST_PATTERNS_FILE = ".test_patterns.yml"
12+ # Match ANSI escape sequences:
13+ # - Standard: ESC followed by various control sequences
14+ # - Orphan CSI: [ followed by parameters and command (when ESC is stripped/lost)
15+ # - OSC hyperlink sequences (preserving URL via capture group)
16+ ANSI_ESCAPE_RE = re .compile (
17+ r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07\x1B]*(?:\x07|\x1B\\)?)' # Standard ANSI/OSC
18+ r'|\[[0-9;]*m' # Orphan CSI color codes
19+ )
20+ # OSC 8 hyperlink pattern: 8;;URL followed by text and closing 8;;
21+ # Captures the URL to preserve it, removes the control sequences
22+ OSC_HYPERLINK_RE = re .compile (r'8;;(https?://[^\s\uFFFD]*)\uFFFD([^\uFFFD]*)\uFFFD?8;;\uFFFD?' )
23+
24+ def strip_ansi_colors (text ):
25+ """Strip ANSI color codes and OSC sequences from text, preserving hyperlink URLs"""
26+ text = ANSI_ESCAPE_RE .sub ('' , text )
27+ # Convert OSC 8 hyperlinks to just the URL
28+ text = OSC_HYPERLINK_RE .sub (r'\1' , text )
29+ return text
1230
1331def run (cmd , input_data = None ):
1432 """Execute command and return (success, stdout, stderr)"""
@@ -32,10 +50,10 @@ def get_pr_number():
3250 return None
3351
3452def read_flakes (file_path ):
35- """Read flake data from file"""
53+ """Read flake data from file, stripping ANSI color codes """
3654 try :
3755 with open (file_path , 'r' ) as f :
38- lines = [line .strip () for line in f if line .strip ()]
56+ lines = [strip_ansi_colors ( line .strip () ) for line in f if line .strip ()]
3957 return lines
4058 except FileNotFoundError :
4159 return []
@@ -48,6 +66,16 @@ def get_comment_id(pr_number):
4866 ])
4967 return out .split ("\n " )[0 ] if ok and out else None
5068
69+ def delete_comment (pr_number ):
70+ """Delete existing flake comment on PR if it exists"""
71+ if comment_id := get_comment_id (pr_number ):
72+ print (f"Deleting comment { comment_id } on PR #{ pr_number } " )
73+ ok , _ , err = run (["gh" , "api" , f"repos/{{owner}}/{{repo}}/issues/comments/{ comment_id } " , "-X" , "DELETE" ])
74+ if not ok :
75+ print (f"Delete failed: { err } " )
76+ return ok
77+ return True # No comment to delete is success
78+
5179def post_comment (pr_number , body ):
5280 """Create or update flake comment on PR"""
5381 if comment_id := get_comment_id (pr_number ):
@@ -122,16 +150,10 @@ def check_flake_thresholds(flake_groups_config, flakes_by_group):
122150
123151 return exceeded
124152
125- def strip_ansi_colors (text ):
126- """Strip ANSI color codes from text"""
127- ansi_escape = re .compile (r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])' )
128- return ansi_escape .sub ('' , text )
129-
130153def build_comment (flakes , threshold_exceeded = None ):
131154 """Build markdown comment body from flake list"""
132155 count = len (flakes )
133- # Strip color codes from flakes for clean markdown display
134- tests = "\n " .join (strip_ansi_colors (flake ) for flake in flakes )
156+ tests = "\n " .join (flakes )
135157
136158 threshold_warning = ""
137159 if threshold_exceeded :
@@ -156,6 +178,11 @@ def main():
156178 flakes = read_flakes (args .flakes_file )
157179 if not flakes :
158180 print (f"No flaked tests found in { args .flakes_file } " )
181+ # Delete existing comment if no flakes found
182+ if args .post_comment :
183+ if pr_number := get_pr_number ():
184+ if delete_comment (pr_number ):
185+ print (f"Deleted existing flake comment on PR #{ pr_number } (no flakes found)" )
159186 return 0
160187 print (f"Found { len (flakes )} flaked test(s)" )
161188
0 commit comments