94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
220 lines
9.8 KiB
YAML
220 lines
9.8 KiB
YAML
name: PR States
|
|
# Maintains the CI-states block at the bottom of the PR body. Uses
|
|
# pull_request_target (not pull_request) for fork-PR write access; safe
|
|
# because we never check out PR head code, only API-read and PATCH the body.
|
|
#
|
|
# Three triggers, each covering a gap:
|
|
# - pull_request_target: push / open / label change (all PRs incl forks).
|
|
# - workflow_run: initial pr-test* completion (incl fork PRs; the
|
|
# notify-job below can't fire on forks — their pull_request
|
|
# GITHUB_TOKEN is forced read-only). Does NOT re-fire for rerun
|
|
# attempts, which is why workflow_dispatch is needed too.
|
|
# - workflow_dispatch: rerun completions for non-fork PRs, dispatched by
|
|
# pr-test*'s notify-pr-states job. workflow_dispatch is the only event
|
|
# GITHUB_TOKEN can cascade-create.
|
|
|
|
on:
|
|
pull_request_target:
|
|
types: [opened, synchronize, reopened, labeled, unlabeled]
|
|
workflow_run:
|
|
workflows: ["PR Test Base", "PR Test Extra"]
|
|
types: [requested, completed]
|
|
workflow_dispatch:
|
|
inputs:
|
|
pr_number:
|
|
description: 'PR number whose CI-states block should be refreshed.'
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
pull-requests: write
|
|
actions: read
|
|
|
|
concurrency:
|
|
# Per-SHA dedupe (PR number for workflow_dispatch). Queues concurrent
|
|
# fires across the three triggers so they don't race on the body PATCH.
|
|
group: pr-states-${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha || inputs.pr_number }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
update-pr-body:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Update CI states block in PR body
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const RETRY_ATTEMPTS = 3;
|
|
const RETRY_DELAY_MS = 6000;
|
|
const LABEL_ON_PRESLEEP_MS = 3000;
|
|
const LABEL_OFF_PRESLEEP_MS = 6000;
|
|
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
|
|
|
// Event-agnostic PR lookup.
|
|
// - pull_request_target: PR is in the payload.
|
|
// - workflow_run: pull_requests[] is populated for same-repo
|
|
// PRs; fork PRs need a head-ref reverse lookup
|
|
// ("forkOwner:branch") because
|
|
// listPullRequestsAssociatedWithCommit requires the commit to
|
|
// be in the default branch. Bail when no open PR matches.
|
|
// - workflow_dispatch: pr_number is an explicit input.
|
|
let prNumber;
|
|
if (context.payload.pull_request) {
|
|
prNumber = context.payload.pull_request.number;
|
|
} else if (context.eventName === 'workflow_run') {
|
|
const wr = context.payload.workflow_run;
|
|
if (wr.pull_requests && wr.pull_requests.length > 0) {
|
|
prNumber = wr.pull_requests[0].number;
|
|
} else if (wr.head_repository && wr.head_branch) {
|
|
const headSpec = `${wr.head_repository.owner.login}:${wr.head_branch}`;
|
|
const { data: prs } = await github.rest.pulls.list({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
state: 'open',
|
|
head: headSpec,
|
|
});
|
|
// Multiple PRs could share a head ref if a branch was reused;
|
|
// match by head_sha to pick the right one.
|
|
const match = prs.find(p => p.head.sha === wr.head_sha) || prs[0];
|
|
if (!match) {
|
|
core.info(`No open PR for head=${headSpec}; skipping.`);
|
|
return;
|
|
}
|
|
prNumber = match.number;
|
|
} else {
|
|
core.info(`workflow_run has no PR linkage; skipping.`);
|
|
return;
|
|
}
|
|
} else if (context.eventName === 'workflow_dispatch') {
|
|
// Reject non-digit pr_number; parseInt('123abc', 10) returns
|
|
// 123 and would silently truncate trailing garbage.
|
|
const raw = context.payload.inputs.pr_number;
|
|
if (!/^\d+$/.test(raw)) {
|
|
core.setFailed(`workflow_dispatch: invalid pr_number input '${raw}' (must be digits only)`);
|
|
return;
|
|
}
|
|
prNumber = parseInt(raw, 10);
|
|
} else {
|
|
core.info(`Unsupported event '${context.eventName}'; skipping.`);
|
|
return;
|
|
}
|
|
const { data: pr } = await github.rest.pulls.get({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber,
|
|
});
|
|
const sha = pr.head.sha;
|
|
const labels = pr.labels.map(l => l.name);
|
|
const hasCI = labels.includes('run-ci');
|
|
const hasExtra = labels.includes('run-ci-extra');
|
|
|
|
// Pre-sleep below only matters when a label was just removed;
|
|
// other no-label fires don't race against fresh indexing.
|
|
const justRemovedLabel = context.eventName === 'pull_request_target'
|
|
&& context.payload.action === 'unlabeled';
|
|
|
|
async function findRunBySha(workflowFile) {
|
|
const { data } = await github.rest.actions.listWorkflowRuns({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
workflow_id: workflowFile,
|
|
head_sha: sha,
|
|
per_page: 1,
|
|
});
|
|
return data.workflow_runs[0] || null;
|
|
}
|
|
async function findRun(workflowFile, { preSleepMs = 0, attempts = 1, delayMs = 0 }) {
|
|
if (preSleepMs > 0) await sleep(preSleepMs);
|
|
for (let i = 0; i < attempts; i++) {
|
|
const run = await findRunBySha(workflowFile);
|
|
if (run) return run;
|
|
if (i < attempts - 1) await sleep(delayMs);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Query unconditionally: removing a label does NOT cancel an
|
|
// in-flight workflow, so a run from before removal can still be
|
|
// live. Label state only picks the fallback text. labelOnOpts
|
|
// covers fresh-push indexing race; labelOffOpts only pre-sleeps
|
|
// on just-removed-label to catch removal-after-push.
|
|
const labelOnOpts = { preSleepMs: LABEL_ON_PRESLEEP_MS, attempts: RETRY_ATTEMPTS, delayMs: RETRY_DELAY_MS };
|
|
const labelOffOpts = { preSleepMs: justRemovedLabel ? LABEL_OFF_PRESLEEP_MS : 0, attempts: 1 };
|
|
const ptRun = await findRun('pr-test.yml', hasCI ? labelOnOpts : labelOffOpts);
|
|
// pr-test-extra gates on BOTH labels (see check-changes there).
|
|
const peRun = await findRun('pr-test-extra.yml', (hasCI && hasExtra) ? labelOnOpts : labelOffOpts);
|
|
|
|
// Skipped run = "no real run" -- happens when a label is added
|
|
// after a commit, because GHA doesn't retrigger on `labeled`.
|
|
const isReal = (run) => run && run.conclusion !== 'skipped';
|
|
|
|
const missingCIText = ':x: **Missing `run-ci` label** -- add it to run CI tests.';
|
|
const peBlockedByCIText = ':x: **Blocked** -- `run-ci` is required first.';
|
|
const notExtraEnabledText = ':warning: **Not enabled** -- add `run-ci-extra` label to opt in.';
|
|
const stalePushText = ':warning: **Not run on latest push** -- push again to dispatch.';
|
|
|
|
// Status icon: body otherwise looks uniformly "dispatched" even
|
|
// for gate-failed runs (call-gate exits on missing label).
|
|
function runIcon(run) {
|
|
if (run.status !== 'completed') return ':hourglass_flowing_sand:';
|
|
switch (run.conclusion) {
|
|
case 'success': return ':white_check_mark:';
|
|
case 'failure': return ':x:';
|
|
case 'cancelled': return ':no_entry_sign:';
|
|
case 'timed_out': return ':alarm_clock:';
|
|
case 'action_required': return ':warning:';
|
|
case 'neutral': return ':grey_question:';
|
|
default: return ':grey_question:';
|
|
}
|
|
}
|
|
const runLink = (run) => `${runIcon(run)} [Run #${run.id}](${run.html_url})`;
|
|
const ptText = isReal(ptRun)
|
|
? runLink(ptRun)
|
|
: (!hasCI ? missingCIText : '_Not run yet_');
|
|
const peText = isReal(peRun)
|
|
? runLink(peRun)
|
|
: !hasCI
|
|
? peBlockedByCIText
|
|
: !hasExtra
|
|
? notExtraEnabledText
|
|
: stalePushText;
|
|
|
|
const outerStart = '<!-- pr-states:start -->';
|
|
const outerEnd = '<!-- pr-states:end -->';
|
|
const ptStart = '<!-- slot:pr-test:start -->';
|
|
const ptEnd = '<!-- slot:pr-test:end -->';
|
|
const peStart = '<!-- slot:pr-test-extra:start -->';
|
|
const peEnd = '<!-- slot:pr-test-extra:end -->';
|
|
|
|
const newBlock = [
|
|
outerStart,
|
|
'---',
|
|
'### CI States',
|
|
'',
|
|
`Latest PR Test (Base): ${ptStart}${ptText}${ptEnd}`,
|
|
`Latest PR Test (Extra): ${peStart}${peText}${peEnd}`,
|
|
outerEnd,
|
|
].join('\n');
|
|
|
|
const body = pr.body || '';
|
|
|
|
const blockRe = new RegExp(`(?:\\n+---\\n+)?${outerStart}[\\s\\S]*?${outerEnd}`);
|
|
let newBody;
|
|
if (blockRe.test(body)) {
|
|
newBody = body.replace(blockRe, `\n\n${newBlock}`);
|
|
} else {
|
|
const sep = body.length === 0
|
|
? ''
|
|
: (body.endsWith('\n') ? '\n' : '\n\n');
|
|
newBody = `${body}${sep}${newBlock}`;
|
|
}
|
|
|
|
if (newBody !== body) {
|
|
await github.rest.pulls.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: prNumber,
|
|
body: newBody,
|
|
});
|
|
}
|