11# This workflow automatically re-runs failed jobs from the Daily CI and PR CI.
2- # It triggers once when either workflow completes, and if any jobs failed,
3- # it re-runs only the failed jobs — but ONLY if no failures are in the
4- # skip list below. If any failure matches the skip list (e.g., fuzz tests),
5- # the retry is skipped to avoid masking non-deterministic test failures.
2+ # It only retries if ALL failures match known infrastructure error patterns
3+ # (e.g., Maven Central outages, Docker pull errors, Windows credential issues).
4+ # If any failure looks like a real test assertion failure, the retry is skipped.
65# It only retries once to avoid infinite loops.
76name : Retry Failed CI
87
1918 permissions :
2019 actions : write
2120 steps :
22- - name : Check failures and retry if appropriate
21+ - name : Check failure patterns and retry if infrastructure-related
2322 uses : actions/github-script@v7
2423 with :
2524 script : |
@@ -36,10 +35,38 @@ jobs:
3635 return;
3736 }
3837
39- // Jobs that should NOT be retried. These are non-deterministic tests
40- // (e.g., fuzz tests) where a retry could mask a real failure.
41- // Use job name prefixes/substrings to match.
42- const skipPatterns = [
38+ // Known infrastructure error patterns that are safe to retry
39+ const infraPatterns = [
40+ // Maven Central outages
41+ 'could not get',
42+ 'could not resolve',
43+ 'status code 502',
44+ 'status code 403',
45+ 'bad gateway',
46+ // Docker/Colima failures on macOS
47+ 'docker: unexpected eof',
48+ 'connection reset by peer',
49+ 'wrong diff id',
50+ // Windows DLL/process crashes
51+ 'exit code -1073741502',
52+ 'exit code -1073741819',
53+ // Windows OIDC credential signing issues
54+ 'invalidsignatureexception',
55+ 'the request signature we calculated does not match',
56+ // Transient DynamoDB errors
57+ 'provisionedthroughputexceededexception',
58+ ];
59+
60+ // Patterns that indicate real test failures — never retry these
61+ const testFailurePatterns = [
62+ 'assertionerror',
63+ 'assertionfailederror',
64+ 'expected:<',
65+ 'nullpointerexception',
66+ ];
67+
68+ // Jobs that should never be retried regardless of error pattern
69+ const skipJobPatterns = [
4370 'fuzz',
4471 ];
4572
@@ -51,22 +78,66 @@ jobs:
5178
5279 const failedJobs = jobs.filter(j => j.conclusion === 'failure');
5380 console.log(`Found ${failedJobs.length} failed job(s):`);
54- failedJobs.forEach(j => console.log(` - ${j.name}`));
81+ failedJobs.forEach(j => console.log(` - ${j.name} (id: ${j.id}) `));
5582
56- // Check if any failed job matches the skip list
57- const skipped = failedJobs.filter(job => {
58- return skipPatterns.some(pattern =>
59- job.name.toLowerCase().includes(pattern.toLowerCase())
60- );
61- });
83+ if (failedJobs.length === 0) {
84+ console.log('No failed jobs found. Skipping.');
85+ return;
86+ }
87+
88+ // Check skip list first
89+ const skippedJobs = failedJobs.filter(job =>
90+ skipJobPatterns.some(p => job.name.toLowerCase().includes(p))
91+ );
92+ if (skippedJobs.length > 0) {
93+ console.log('Skip-listed job(s) failed. Not retrying:');
94+ skippedJobs.forEach(j => console.log(` - ${j.name}`));
95+ return;
96+ }
97+
98+ // Check each failed job's logs
99+ let allInfra = true;
100+ for (const job of failedJobs) {
101+ console.log(`\nAnalyzing logs for: ${job.name}`);
102+ let logs;
103+ try {
104+ const response = await github.rest.actions.downloadJobLogsForWorkflowRun({
105+ owner: context.repo.owner,
106+ repo: context.repo.repo,
107+ job_id: job.id,
108+ });
109+ logs = response.data.toLowerCase();
110+ } catch (e) {
111+ console.log(` Could not fetch logs: ${e.message}. Assuming real failure.`);
112+ allInfra = false;
113+ break;
114+ }
115+
116+ // Check for real test failures first
117+ const hasTestFailure = testFailurePatterns.some(p => logs.includes(p));
118+ if (hasTestFailure) {
119+ console.log(` Found test assertion failure. Not retrying.`);
120+ allInfra = false;
121+ break;
122+ }
123+
124+ // Check if failure matches known infra patterns
125+ const matchedInfra = infraPatterns.filter(p => logs.includes(p));
126+ if (matchedInfra.length > 0) {
127+ console.log(` Matched infra patterns: ${matchedInfra.join(', ')}`);
128+ } else {
129+ console.log(` No known infra pattern matched. Assuming real failure.`);
130+ allInfra = false;
131+ break;
132+ }
133+ }
62134
63- if (skipped.length > 0) {
64- console.log('Failures in skip-listed jobs found. Skipping retry:');
65- skipped.forEach(j => console.log(` - ${j.name}`));
135+ if (!allInfra) {
136+ console.log('\nReal test failure detected. Skipping retry.');
66137 return;
67138 }
68139
69- console.log('No skip-listed failures. Re-running failed jobs...');
140+ console.log('\nAll failures are infrastructure-related . Re-running failed jobs...');
70141 await github.rest.actions.reRunWorkflowFailedJobs({
71142 owner: context.repo.owner,
72143 repo: context.repo.repo,
0 commit comments