Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions .github/workflows/pr-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
actions: read
name: "PR Gate: Backend"
runs-on: ubuntu-latest
timeout-minutes: 45
timeout-minutes: 150
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
Expand Down Expand Up @@ -116,13 +116,15 @@ jobs:
github-token: ${{ github.token }}
# "Backend CI Status Gate" is the final aggregate job of
# backend-ci.yml. It is not created until every other backend job
# finishes (~8 min on a green run), so we poll up to 40 minutes for it
# to appear and complete. The job's timeout-minutes: 45 remains the hard bound.
# finishes (up to 80 min under runner contention), so we poll up to
# 140 minutes for it to appear and complete. The job's 150-minute
# timeout remains the hard bound.
script: |
const { owner, repo } = context.repo;
const sha = process.env.HEAD_SHA;
const CHECK_NAME = process.env.CHECK_NAME;
const deadline = Date.now() + 40 * 60 * 1000;
const started = Date.now();
const deadline = started + 140 * 60 * 1000;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const callWithRetry = async (fn) => {
Expand Down Expand Up @@ -190,6 +192,7 @@ jobs:
};

let delay = 30000;
let completedWithoutGate = false;
while (Date.now() < deadline) {
const res = await callWithRetry(() =>
github.rest.checks.listForRef({
Expand All @@ -216,6 +219,36 @@ jobs:
return;
}

const workflowRes = await callWithRetry(() =>
github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: 'backend-ci.yml',
head_sha: sha,
event: 'pull_request',
per_page: 20,
})
);
if (!workflowRes) {
break;
}
const workflowRuns = workflowRes.data?.workflow_runs || [];
if (workflowRuns.length === 0 && Date.now() - started >= 10 * 60 * 1000) {
core.setFailed('Backend paths changed but Backend CI was not triggered within 10 minutes; path lists in pr-gate.yml and backend-ci.yml may have drifted.');
return;
}
if (runs.length === 0 && workflowRuns.length > 0 && workflowRuns.every(r => r.status === 'completed')) {
if (completedWithoutGate) {
core.setFailed(`Backend CI completed without ${CHECK_NAME} check run. Run conclusion: ${workflowRuns.map(r => r.conclusion || 'unknown').join(', ')}`);
return;
}
completedWithoutGate = true;
core.info(`Backend CI completed (${workflowRuns.map(r => r.conclusion || 'unknown').join(', ')}); rechecking for ${CHECK_NAME}.`);
} else {
completedWithoutGate = false;
core.info(`Backend CI run status: ${workflowRuns.length ? workflowRuns.map(r => r.status).join(', ') : 'not triggered yet'}.`);
}

const remaining = Math.max(0, deadline - Date.now());
if (remaining === 0) {
break;
Expand Down
Loading