Skip to content

Commit 909b34a

Browse files
authored
chore(ci): retry failed CI only for infrastructure errors (#2235)
1 parent 0140037 commit 909b34a

1 file changed

Lines changed: 91 additions & 20 deletions

File tree

.github/workflows/retry_daily_ci.yml

Lines changed: 91 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
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.
76
name: Retry Failed CI
87

@@ -19,7 +18,7 @@ jobs:
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

Comments
 (0)