Skip to content

Aggregate CI check status #6152

Aggregate CI check status

Aggregate CI check status #6152

Workflow file for this run

name: CI Status
run-name: Aggregate CI check status
on:
pull_request:
branches:
- main
- 'release-[0-9]+.[0-9]+.x'
merge_group:
branches:
- main
- 'release-[0-9]+.[0-9]+.x'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
ci-status:
runs-on: ubuntu-latest
timeout-minutes: 180
permissions:
checks: read
pull-requests: read
steps:
- name: Wait for CI checks to complete
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const sha = context.payload.pull_request?.head.sha ?? context.sha;
const owner = context.repo.owner;
const repo = context.repo.repo;
// Wait for other workflows to get queued
core.info('Waiting 60s for CI workflows to get queued...');
await new Promise(r => setTimeout(r, 60000));
const excludedChecks = new Set([
'ci-status',
'Run Integration Tests with Latest Release (Informational)',
'Check Schema Compatibility with Latest Release (Informational)',
]);
const excludedApps = new Set(['mergify']);
const terminalStatuses = new Set(['completed']);
const successConclusions = new Set(['success', 'skipped', 'neutral']);
const failureConclusions = new Set(['failure', 'timed_out', 'cancelled', 'action_required', 'startup_failure', 'stale']);
const replayCheckPrefix = 'Integration Tests (';
const recordCheckPrefix = 'record-providers (';
function pathTriggersReplay(path) {
if (path.startsWith('src/ogx_ui/')) return false;
return path.startsWith('src/ogx/') ||
path.startsWith('tests/') ||
path === 'uv.lock' ||
path === 'pyproject.toml' ||
path === '.github/workflows/integration-tests.yml' ||
path === '.github/actions/setup-ollama/action.yml' ||
path === '.github/actions/setup-test-environment/action.yml' ||
path === '.github/actions/run-and-record-tests/action.yml' ||
path === 'scripts/integration-tests.sh' ||
path === 'scripts/generate_ci_matrix.py';
}
let replayRequired = context.eventName === 'merge_group';
if (context.payload.pull_request) {
const changedFiles = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: context.payload.pull_request.number,
per_page: 100,
});
replayRequired = changedFiles.some(file => pathTriggersReplay(file.filename));
core.info(`Replay required: ${replayRequired} (${changedFiles.length} changed file(s) checked)`);
}
while (true) {
const checkRuns = await github.paginate(github.rest.checks.listForRef, {
owner,
repo,
ref: sha,
per_page: 100,
});
// Filter to only GitHub Actions checks, excluding ourselves and bots
const relevant = checkRuns.filter(cr => {
if (excludedChecks.has(cr.name)) return false;
if (cr.app && excludedApps.has(cr.app.slug)) return false;
// Only include GitHub Actions checks
if (!cr.app || cr.app.slug !== 'github-actions') return false;
return true;
});
if (relevant.length === 0) {
core.info('No other CI checks found yet, waiting...');
await new Promise(r => setTimeout(r, 30000));
continue;
}
const pending = relevant.filter(cr => !terminalStatuses.has(cr.status));
const completed = relevant.filter(cr => terminalStatuses.has(cr.status));
const replayChecks = relevant.filter(cr => cr.name.startsWith(replayCheckPrefix));
const recordChecks = relevant.filter(cr => cr.name.startsWith(recordCheckPrefix));
core.info(`Checks: ${completed.length} completed, ${pending.length} pending out of ${relevant.length} total`);
core.info(`Replay checks: ${replayChecks.length}; record checks: ${recordChecks.length}`);
for (const cr of completed) {
core.info(` ✓ ${cr.name}: ${cr.conclusion}`);
}
for (const cr of pending) {
core.info(` ⏳ ${cr.name}: ${cr.status}`);
}
if (pending.length > 0) {
core.info('Waiting 30s for pending checks...');
await new Promise(r => setTimeout(r, 30000));
continue;
}
if (replayRequired && replayChecks.length === 0) {
if (recordChecks.length > 0) {
core.warning('Record checks are present, but no replay matrix checks were found for this SHA.');
}
core.info('Replay checks are required for this change. Waiting 30s for the replay workflow to appear...');
await new Promise(r => setTimeout(r, 30000));
continue;
}
// All checks completed — evaluate conclusions
const failed = completed.filter(cr => failureConclusions.has(cr.conclusion));
if (failed.length > 0) {
for (const cr of failed) {
core.warning(`${cr.name} concluded with: ${cr.conclusion} — waiting for re-run`);
}
core.info(`${failed.length} check(s) failed. Waiting 30s for re-runs before giving up...`);
core.info('Re-run the failed job(s) and ci-status will pick up the result automatically.');
await new Promise(r => setTimeout(r, 30000));
continue;
}
const succeeded = completed.filter(cr => successConclusions.has(cr.conclusion));
core.info(`All ${succeeded.length} CI checks passed.`);
return;
}