Skip to content

Commit d4b0c7d

Browse files
committed
fix(ci): debounce canonical main runner admission
1 parent 3cc05d5 commit d4b0c7d

3 files changed

Lines changed: 77 additions & 26 deletions

File tree

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,32 @@ env:
4141
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
4242

4343
jobs:
44+
# Keep the canonical main queue quiet long enough for a follow-up push to
45+
# cancel this run before it registers the Blacksmith matrix.
46+
runner-admission:
47+
permissions:
48+
contents: read
49+
runs-on: ubuntu-24.04
50+
timeout-minutes: 3
51+
env:
52+
OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS: "90"
53+
steps:
54+
- name: Debounce canonical main pushes
55+
if: github.event_name == 'push' && github.repository == 'openclaw/openclaw' && github.ref == 'refs/heads/main'
56+
run: |
57+
set -euo pipefail
58+
echo "Waiting ${OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS}s for a superseding main push before Blacksmith admission"
59+
sleep "${OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS}"
60+
- name: Admit non-main CI runs immediately
61+
if: github.event_name != 'push' || github.repository != 'openclaw/openclaw' || github.ref != 'refs/heads/main'
62+
run: echo "No canonical main debounce required"
63+
4464
# Preflight: establish routing truth and job matrices once, then let real
4565
# work fan out from a single source of truth.
4666
preflight:
4767
permissions:
4868
contents: read
69+
needs: [runner-admission]
4970
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
5071
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
5172
timeout-minutes: 20
@@ -361,6 +382,7 @@ jobs:
361382
security-fast:
362383
permissions:
363384
contents: read
385+
needs: [runner-admission]
364386
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
365387
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
366388
timeout-minutes: 20

docs/ci.md

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,51 @@ read_when:
88
- You are changing ClawSweeper dispatch or GitHub activity forwarding
99
---
1010

11-
OpenClaw CI runs on every push to `main` and every pull request. The `preflight` job classifies the diff and turns expensive lanes off when only unrelated areas changed. Manual `workflow_dispatch` runs intentionally bypass smart scoping and fan out the full graph for release candidates and broad validation. Android lanes stay opt-in through `include_android`. Release-only plugin coverage lives in the separate [`Plugin Prerelease`](#plugin-prerelease) workflow and only runs from [`Full Release Validation`](#full-release-validation) or an explicit manual dispatch.
11+
OpenClaw CI runs on every push to `main` and every pull request. Canonical
12+
`main` pushes first pass through a 90-second hosted-runner admission window.
13+
The existing `CI` concurrency group cancels that waiting run when a newer
14+
commit lands, so sequential merges do not each register a full Blacksmith
15+
matrix. Pull requests and manual dispatches skip the wait. The `preflight` job
16+
then classifies the diff and turns expensive lanes off when only unrelated
17+
areas changed. Manual `workflow_dispatch` runs intentionally bypass smart
18+
scoping and fan out the full graph for release candidates and broad
19+
validation. Android lanes stay opt-in through `include_android`. Release-only
20+
plugin coverage lives in the separate [`Plugin Prerelease`](#plugin-prerelease)
21+
workflow and only runs from [`Full Release Validation`](#full-release-validation)
22+
or an explicit manual dispatch.
1223

1324
## Pipeline overview
1425

15-
| Job | Purpose | When it runs |
16-
| ---------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------- |
17-
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
18-
| `security-fast` | Private key detection, changed-workflow audit via `zizmor`, and production lockfile audit | Always on non-draft pushes and PRs |
19-
| `check-dependencies` | Production Knip dependency-only pass plus the unused-file allowlist guard | Node-relevant changes |
20-
| `build-artifacts` | Build `dist/`, Control UI, built-CLI smoke checks, embedded built-artifact checks, and reusable artifacts | Node-relevant changes |
21-
| `checks-fast-core` | Fast Linux correctness lanes such as bundled, protocol, and CI-routing checks | Node-relevant changes |
22-
| `checks-fast-contracts-plugins-*` | Two sharded plugin contract checks | Node-relevant changes |
23-
| `checks-fast-contracts-channels-*` | Two sharded channel contract checks | Node-relevant changes |
24-
| `checks-node-core-*` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
25-
| `check-*` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes |
26-
| `check-additional-*` | Architecture, sharded boundary/prompt drift, extension guards, package boundary, and runtime topology | Node-relevant changes |
27-
| `checks-node-compat-node22` | Node 22 compatibility build and smoke lane | Manual CI dispatch for releases |
28-
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
29-
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
30-
| `checks-windows` | Windows-specific process/path tests plus shared runtime import specifier regressions | Windows-relevant changes |
31-
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
32-
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
33-
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
34-
| `test-performance-agent` | Daily Codex slow-test optimization after trusted activity | Main CI success or manual dispatch |
35-
| `openclaw-performance` | Daily/on-demand Kova runtime performance reports with mock-provider, deep-profile, and GPT 5.5 live lanes | Scheduled and manual dispatch |
26+
| Job | Purpose | When it runs |
27+
| ---------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
28+
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
29+
| `runner-admission` | Hosted 90-second debounce for canonical `main` pushes before Blacksmith work is registered | Every CI run; sleep only on canonical `main` pushes |
30+
| `security-fast` | Private key detection, changed-workflow audit via `zizmor`, and production lockfile audit | Always on non-draft pushes and PRs |
31+
| `check-dependencies` | Production Knip dependency-only pass plus the unused-file allowlist guard | Node-relevant changes |
32+
| `build-artifacts` | Build `dist/`, Control UI, built-CLI smoke checks, embedded built-artifact checks, and reusable artifacts | Node-relevant changes |
33+
| `checks-fast-core` | Fast Linux correctness lanes such as bundled, protocol, and CI-routing checks | Node-relevant changes |
34+
| `checks-fast-contracts-plugins-*` | Two sharded plugin contract checks | Node-relevant changes |
35+
| `checks-fast-contracts-channels-*` | Two sharded channel contract checks | Node-relevant changes |
36+
| `checks-node-core-*` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
37+
| `check-*` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes |
38+
| `check-additional-*` | Architecture, sharded boundary/prompt drift, extension guards, package boundary, and runtime topology | Node-relevant changes |
39+
| `checks-node-compat-node22` | Node 22 compatibility build and smoke lane | Manual CI dispatch for releases |
40+
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
41+
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
42+
| `checks-windows` | Windows-specific process/path tests plus shared runtime import specifier regressions | Windows-relevant changes |
43+
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
44+
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
45+
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
46+
| `test-performance-agent` | Daily Codex slow-test optimization after trusted activity | Main CI success or manual dispatch |
47+
| `openclaw-performance` | Daily/on-demand Kova runtime performance reports with mock-provider, deep-profile, and GPT 5.5 live lanes | Scheduled and manual dispatch |
3648

3749
## Fail-fast order
3850

39-
1. `preflight` decides which lanes exist at all. The `docs-scope` and `changed-scope` logic are steps inside this job, not standalone jobs.
40-
2. `security-fast`, `check-*`, `check-additional-*`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs.
41-
3. `build-artifacts` overlaps with the fast Linux lanes so downstream consumers can start as soon as the shared build is ready.
42-
4. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-plugins-*`, `checks-fast-contracts-channels-*`, `checks-node-core-*`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
51+
1. `runner-admission` waits only for canonical `main` pushes; a newer push cancels the run before Blacksmith registration.
52+
2. `preflight` decides which lanes exist at all. The `docs-scope` and `changed-scope` logic are steps inside this job, not standalone jobs.
53+
3. `security-fast`, `check-*`, `check-additional-*`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs.
54+
4. `build-artifacts` overlaps with the fast Linux lanes so downstream consumers can start as soon as the shared build is ready.
55+
5. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-plugins-*`, `checks-fast-contracts-channels-*`, `checks-node-core-*`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
4356

4457
GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. Matrix jobs use `fail-fast: false`, and `build-artifacts` reports embedded channel, core-support-boundary, and gateway-watch failures directly instead of queuing tiny verifier jobs. The automatic CI concurrency key is versioned (`CI-v7-*`) so a GitHub-side zombie in an old queue group cannot indefinitely block newer main runs. Manual full-suite runs use `CI-manual-v1-*` and do not cancel in-progress runs.
4558

test/scripts/ci-workflow-guards.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,22 @@ describe("ci workflow guards", () => {
118118
expect(workflow.jobs.android.strategy["max-parallel"]).toBe(2);
119119
});
120120

121+
it("debounces canonical main pushes before Blacksmith admission", () => {
122+
const workflow = readCiWorkflow();
123+
const source = readFileSync(".github/workflows/ci.yml", "utf8");
124+
const admission = workflow.jobs["runner-admission"];
125+
126+
expect(admission["runs-on"]).toBe("ubuntu-24.04");
127+
expect(admission.steps[0].if).toContain("github.ref == 'refs/heads/main'");
128+
expect(admission.steps[0].run).toContain('sleep "${OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS}"');
129+
expect(admission.env.OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS).toBe("90");
130+
expect(workflow.jobs.preflight.needs).toContain("runner-admission");
131+
expect(workflow.jobs["security-fast"].needs).toContain("runner-admission");
132+
expect(source).toContain(
133+
"cancel-in-progress: ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && github.repository == 'openclaw/openclaw' && github.ref == 'refs/heads/main') }}",
134+
);
135+
});
136+
121137
it("uses bundled Node shards and telemetry-backed runner sizes", () => {
122138
const workflow = readCiWorkflow();
123139
const source = readFileSync(".github/workflows/ci.yml", "utf8");

0 commit comments

Comments
 (0)