Skip to content

Commit f8aeedb

Browse files
committed
fix: skip orphaned check suites when checking GitHub CI
`checkGitHubCI()` blocks landing with "GitHub CI is still running" whenever any GitHub Actions check suite has a status other than COMPLETED. GitHub occasionally creates check suites that stay stuck in QUEUED with zero check runs and are never dispatched, so they never complete. These orphaned suites block the commit queue indefinitely and force collaborators to land PRs manually. Skip a non-completed suite only when it has no check runs and was created more than 3 hours ago, since a legitimately queued suite gets its runs attached within seconds. Suites with active runs still block as before, and a fresh empty suite (within the window) is not skipped. Adds `createdAt` to the checkSuites GraphQL query to compute suite age. Refs: #1160 Signed-off-by: Naman Trivedi <trivenay@amazon.com>
1 parent c53f184 commit f8aeedb

4 files changed

Lines changed: 76 additions & 1 deletion

File tree

lib/pr_checker.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ const WAIT_TIME_SINGLE_APPROVAL = 24 * 7;
2525

2626
const GITHUB_SUCCESS_CONCLUSIONS = ['SUCCESS', 'NEUTRAL', 'SKIPPED'];
2727
const GITHUB_ACTIONS_APP = 'github-actions';
28+
// A GitHub Actions check suite with no runs that has not started after this
29+
// long is considered orphaned (created but never dispatched by GitHub) and is
30+
// ignored so it does not block landing. See nodejs/node-core-utils#1160.
31+
const ORPHANED_CHECK_SUITE_TIMEOUT = MINUTE * 60 * 3;
2832

2933
const FAST_TRACK_RE = /^Fast-track has been requested by @(.+?)\. Please 👍 to approve\.$/;
3034
const FAST_TRACK_MIN_APPROVALS = 2;
@@ -460,8 +464,17 @@ export default class PRChecker {
460464
const pendingJobs = [];
461465

462466
// GitHub new Check API
463-
for (const { status, conclusion, checkRuns } of checkSuites.nodes) {
467+
for (const { status, conclusion, checkRuns, createdAt } of checkSuites.nodes) {
464468
if (status !== 'COMPLETED') {
469+
// Skip orphaned check suites: no runs and never dispatched by GitHub.
470+
// They will never complete, so they should not block landing.
471+
const runCount = checkRuns?.nodes?.length ?? 0;
472+
const age = Date.now() - new Date(createdAt).getTime();
473+
if (runCount === 0 && age > ORPHANED_CHECK_SUITE_TIMEOUT) {
474+
cli.warn('Ignoring orphaned check suite with no runs ' +
475+
`(status: ${status}, age: ${Math.round(age / MINUTE / 60)}h)`);
476+
continue;
477+
}
465478
pendingJobs.push({ status, conclusion });
466479
continue;
467480
}

lib/queries/PR.gql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ query PR($prid: Int!, $owner: String!, $repo: String!) {
3535
# https://api.github.com/apps/github-actions
3636
checkSuites(first: 100, filterBy: { appId: 15368 }) {
3737
nodes {
38+
createdAt,
3839
conclusion,
3940
status,
4041
checkRuns(first: 40) {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[
2+
{
3+
"commit": {
4+
"committedDate": "2017-10-26T12:10:20Z",
5+
"oid": "9d098ssiskj8dhd39js0sjd0cn2ng4is9n40sj12d",
6+
"messageHeadline": "doc: add api description README",
7+
"author": {
8+
"login": "foo"
9+
},
10+
"checkSuites": {
11+
"nodes": [
12+
{
13+
"status": "COMPLETED",
14+
"conclusion": "SUCCESS",
15+
"createdAt": "2024-01-01T00:00:00Z",
16+
"checkRuns": {
17+
"nodes": [
18+
{
19+
"name": "test-linux",
20+
"status": "COMPLETED",
21+
"conclusion": "SUCCESS",
22+
"detailsUrl": "https://example.com"
23+
}
24+
]
25+
}
26+
},
27+
{
28+
"status": "QUEUED",
29+
"conclusion": null,
30+
"createdAt": "2024-01-01T00:00:00Z",
31+
"checkRuns": {
32+
"nodes": []
33+
}
34+
}
35+
]
36+
}
37+
}
38+
}
39+
]

test/unit/pr_checker.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,6 +1803,28 @@ describe('PRChecker', () => {
18031803
cli.assertCalledWith(expectedLogs);
18041804
});
18051805

1806+
it('should skip orphaned check suite with no runs older than the timeout',
1807+
async() => {
1808+
const cli = new TestCLI();
1809+
1810+
const expectedLogs = {
1811+
ok: [
1812+
['Last GitHub CI successful']
1813+
]
1814+
};
1815+
1816+
const commits = githubCI['check-suite-orphaned'];
1817+
const data = Object.assign({}, baseData, { commits });
1818+
1819+
const checker = new PRChecker(cli, data, {}, testArgv);
1820+
1821+
const status = await checker.checkCI();
1822+
assert(status);
1823+
// Ignore the warn channel: it reports the orphaned suite's age, which
1824+
// is computed from the current time and is therefore non-deterministic.
1825+
cli.assertCalledWith(expectedLogs, { ignore: ['warn'] });
1826+
});
1827+
18061828
it('should error if commit status failed', async() => {
18071829
const cli = new TestCLI();
18081830

0 commit comments

Comments
 (0)