fix: keep CI running during Blacksmith outages (#123263)

* ci: add runner-backend circuit breaker with GitHub-hosted fallback

* fix(ci): extend hosted timeouts to circuit-breaker runs
This commit is contained in:
Peter Steinberger
2026-08-13 10:18:35 -07:00
committed by GitHub
parent 9550d6f962
commit fb82d87cad
4 changed files with 295 additions and 84 deletions
@@ -175,12 +175,42 @@ These are intentionally guarded by `test/scripts/ci-workflow-guards.test.ts`:
- lower-weight Node/check shards on `blacksmith-4vcpu-ubuntu-2404`.
- heavy retained Linux/Android shards on `blacksmith-8vcpu-ubuntu-2404`.
- CodeQL Critical Quality on `ubuntu-24.04` with no `blacksmith-` labels.
- `OPENCLAW_CI_RUNNER_BACKEND=github` routes every configurable `ci.yml` job
to its existing GitHub-hosted fallback label. Unset or `blacksmith` preserves
the normal Blacksmith-first route.
- Vitest/test compile caches are restore-only in CI and use immutable Actions
caches; the daily/dispatch warmer is their sole writer. Build compile cache
writes rotate at most once per UTC day. PRs create no runtime-cache archives.
When changing one knob, update `docs/ci.md` and the guard test in the same PR.
## Blacksmith Outage Circuit Breaker
Use the repository variable only after confirming a Blacksmith outage or
unavailable runner capacity. Do not set it merely for a failing test that has
already started.
```bash
gh variable set OPENCLAW_CI_RUNNER_BACKEND --repo openclaw/openclaw --body github
```
In degraded mode, `ci.yml` uses the same hosted labels and non-Blacksmith paths
as manual dispatches and fork pull requests. Blacksmith-only Docker and sticky
steps stay off, dependency setup uses the ordinary Actions pnpm-store cache,
and Android's large build uses separate low-memory Gradle processes. Standard
4-core hosted runners make builds and test lanes slower. Blacksmith runner
registration is no longer part of the budget, while GitHub-hosted concurrency
limits apply.
Flip back after the outage by deleting the variable:
```bash
gh variable delete OPENCLAW_CI_RUNNER_BACKEND --repo openclaw/openclaw
```
Scheduled health detection and automatic flipping are a follow-up, not part of
the current circuit breaker.
## Validation
For workflow-only or docs/skill-only changes in a Codex worktree:
+78 -76
View File
@@ -73,7 +73,7 @@ jobs:
permissions:
contents: read
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
outputs:
checkout_revision: ${{ steps.checkout_ref.outputs.sha }}
@@ -871,10 +871,11 @@ jobs:
run: node scripts/check-protocol-event-coverage.mjs
# Publish one immutable semantic dependency archive before same-repo
# Blacksmith jobs fan out. Pull-request archives remain merge-ref scoped;
# Blacksmith jobs fan out. The GitHub backend uses the pnpm-store cache.
# Pull-request archives remain merge-ref scoped;
# main archives seed later pull requests through the default-branch scope.
- name: Publish exact dependency cache
if: github.repository == 'openclaw/openclaw' && steps.manifest.outputs.run_node == 'true' && ((github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
if: vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.repository == 'openclaw/openclaw' && steps.manifest.outputs.run_node == 'true' && ((github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
uses: ./.github/actions/setup-node-env
with:
dependency-cache: "true"
@@ -1088,14 +1089,14 @@ jobs:
run: node scripts/pre-commit/pnpm-audit-prod.mjs --audit-level=high
# Warm the lockfile- and pnpm-pinned Actions cache for fork PRs, manual runs,
# and docs-only same-repo PRs. Node-relevant canonical main and same-repo PRs
# already publish it through the exact dependency-cache writer in preflight.
# the GitHub backend, and docs-only same-repo PRs. Node-relevant Blacksmith
# runs already publish it through the exact dependency-cache writer in preflight.
pnpm-store-warmup:
permissions:
contents: read
needs: [preflight]
if: ${{ (needs.preflight.outputs.run_node == 'true' || needs.preflight.outputs.run_check_docs == 'true') && !(github.repository == 'openclaw/openclaw' && github.event_name == 'push' && github.ref == 'refs/heads/main') && !(github.repository == 'openclaw/openclaw' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && needs.preflight.outputs.run_node == 'true') }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
if: ${{ (needs.preflight.outputs.run_node == 'true' || needs.preflight.outputs.run_check_docs == 'true') && (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || (!(github.repository == 'openclaw/openclaw' && github.event_name == 'push' && github.ref == 'refs/heads/main') && !(github.repository == 'openclaw/openclaw' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && needs.preflight.outputs.run_node == 'true'))) }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
steps:
- &linux_node_checkout_step
@@ -1165,8 +1166,8 @@ jobs:
# 32 vCPU: the dist build sits on the light-run critical path alongside
# check-lint/check-dependencies; tsdown parallelizes across the extra
# cores for roughly the same billed core-minutes.
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-32vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 35 || 20 }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-32vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: ${{ (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository)) && 35 || 20 }}
outputs:
channels-result: ${{ steps.built_artifact_checks.outputs['channels-result'] }}
core-support-boundary-result: ${{ steps.built_artifact_checks.outputs['core-support-boundary-result'] }}
@@ -1189,8 +1190,8 @@ jobs:
node-compile-cache-scope: "build"
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
save-node-compile-cache: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'true' || 'false' }}
- name: Restore build-all step cache
@@ -1448,7 +1449,7 @@ jobs:
name: check-sqlite-session-lifecycle
needs: [preflight, build-artifacts]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_sqlite_session_lifecycle == 'true' && needs.build-artifacts.result == 'success' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
steps:
- *linux_node_checkout_step
@@ -1456,8 +1457,8 @@ jobs:
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Download exact-run built runtime
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
@@ -1479,7 +1480,7 @@ jobs:
contents: read
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_native_i18n == 'true' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 10
steps:
- name: Checkout
@@ -1494,8 +1495,8 @@ jobs:
install-bun: "false"
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Verify native app i18n source
run: |
@@ -1523,7 +1524,7 @@ jobs:
name: checks-ui
needs: [preflight]
if: needs.preflight.outputs.run_ui_tests == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
env:
COMPATIBILITY_TARGET: ${{ needs.preflight.outputs.compatibility_target }}
@@ -1536,8 +1537,8 @@ jobs:
install-bun: "false"
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- &install_playwright_chromium
name: Install Playwright Chromium
@@ -1582,9 +1583,9 @@ jobs:
# Compatibility targets pin a frozen Control UI whose e2e expectations track
# that release, not current main.
if: needs.preflight.outputs.run_ui_tests == 'true' && needs.preflight.outputs.compatibility_target != 'true'
# First-attempt same-repo runs use Blacksmith; manual dispatches, forks, and
# same-repo PR retries use GitHub-hosted capacity as an independent fallback.
runs-on: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
# First-attempt same-repo runs use Blacksmith unless the backend breaker is
# set; manual dispatches, forks, and same-repo PR retries also use GitHub-hosted capacity.
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
# Keep Chromium ownership serial within each independently required shard.
timeout-minutes: 25
env:
@@ -1603,8 +1604,8 @@ jobs:
install-bun: "false"
# Hosted paths use the pnpm store cache; first-attempt same-repo
# Blacksmith runs restore the preflight-published dependency tree.
dependency-cache: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'false' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false') }}
use-actions-cache: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'true' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true') }}
dependency-cache: ${{ (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'false' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false') }}
use-actions-cache: ${{ (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'true' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true') }}
- *install_playwright_chromium
@@ -1628,9 +1629,9 @@ jobs:
# Compatibility targets pin a frozen Control UI whose e2e expectations track
# that release, not current main.
if: needs.preflight.outputs.run_ui_tests == 'true' && needs.preflight.outputs.compatibility_target != 'true'
# First-attempt same-repo runs use Blacksmith; manual dispatches, forks, and
# same-repo PR retries use GitHub-hosted capacity as an independent fallback.
runs-on: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
# First-attempt same-repo runs use Blacksmith unless the backend breaker is
# set; manual dispatches, forks, and same-repo PR retries also use GitHub-hosted capacity.
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
steps:
- *linux_node_checkout_step
@@ -1641,8 +1642,8 @@ jobs:
install-bun: "false"
# Hosted paths use the pnpm store cache; first-attempt same-repo
# Blacksmith runs restore the preflight-published dependency tree.
dependency-cache: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'false' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false') }}
use-actions-cache: ${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'true' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true') }}
dependency-cache: ${{ (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'false' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false') }}
use-actions-cache: ${{ (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'true' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true') }}
- *install_playwright_chromium
@@ -1666,7 +1667,7 @@ jobs:
name: control-ui-i18n
needs: [preflight]
if: needs.preflight.outputs.run_control_ui_i18n == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 10
env:
COMPATIBILITY_TARGET: ${{ needs.preflight.outputs.compatibility_target }}
@@ -1679,8 +1680,8 @@ jobs:
install-bun: "false"
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Verify Control UI i18n source
run: |
@@ -1706,7 +1707,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_fast_core == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1767,8 +1768,8 @@ jobs:
install-bun: ${{ matrix.task == 'bun-launcher' && 'true' || 'false' }}
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
env:
@@ -1844,7 +1845,7 @@ jobs:
name: QA Smoke CI (${{ matrix.name }})
needs: [preflight]
if: needs.preflight.outputs.run_qa_smoke_ci == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -1872,11 +1873,11 @@ jobs:
install-bun: "false"
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Set up Blacksmith Docker layer cache
if: ${{ matrix.docker_cache == true && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') }}
if: ${{ matrix.docker_cache == true && vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') }}
uses: useblacksmith/setup-docker-builder@6ff44f8e5255f9d8aa31ef22f7e57a2d926b7da0 # v1
with:
max-cache-size-mb: 800000
@@ -1908,7 +1909,7 @@ jobs:
env:
PROFILE_PART: ${{ matrix.lane }}
PROFILE_PART_SLUG: ${{ matrix.slug }}
OPENCLAW_QA_SUITE_WORKER_START_STAGGER_MS: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && '0' || '1500' }}
OPENCLAW_QA_SUITE_WORKER_START_STAGGER_MS: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && '0' || '1500' }}
shell: bash
run: |
set -euo pipefail
@@ -2038,7 +2039,7 @@ jobs:
name: ${{ matrix.checkName }}
needs: [preflight]
if: needs.preflight.outputs.run_plugin_contracts_shards == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -2052,8 +2053,8 @@ jobs:
install-bun: "false"
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Run plugin contract shard
env:
@@ -2080,7 +2081,7 @@ jobs:
name: ${{ matrix.checkName }}
needs: [preflight]
if: needs.preflight.outputs.run_channel_contracts_shards == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
strategy:
fail-fast: false
@@ -2094,8 +2095,8 @@ jobs:
install-bun: "false"
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Run channel contract shard
env:
@@ -2123,7 +2124,7 @@ jobs:
name: checks-node-compat-node22
needs: [preflight]
if: needs.preflight.outputs.run_build_artifacts == 'true' && github.event_name == 'workflow_dispatch'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 60
steps:
- *linux_node_checkout_step
@@ -2153,7 +2154,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_node_core_nondist == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
timeout-minutes: ${{ matrix.timeout_minutes || 60 }}
strategy:
fail-fast: false
@@ -2170,8 +2171,8 @@ jobs:
install-bun: "false"
# Only the preflight writer saves an archive. Same-repo Blacksmith
# shards restore it; hosted and non-Node-24 paths use the pnpm store.
dependency-cache: ${{ (matrix.node_version == null || matrix.node_version == '24.x') && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ (matrix.node_version == null || matrix.node_version == '24.x') && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ (matrix.node_version == null || matrix.node_version == '24.x') && vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ (matrix.node_version == null || matrix.node_version == '24.x') && vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
vitest-fs-cache: "true"
node-compile-cache: "true"
node-compile-cache-scope: "test"
@@ -2264,7 +2265,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check == 'true' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -2306,15 +2307,15 @@ jobs:
install-bun: "false"
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
# check-lint's shard runner rebuilds the same plugin-sdk boundary
# artifacts the boundary lane snapshots (~72s cold); restore them from
# the shared sticky. Strictly read-only (commit: false): only the
# protected boundary lane may publish this repository-global snapshot.
- name: Mount extension boundary sticky disk
if: matrix.task == 'lint' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
if: matrix.task == 'lint' && vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
uses: useblacksmith/stickydisk@6d373c96a74cbde0c99fedc5ea5d3a7ba66ba494 # main (post-v1.4.0 hot-attach fix)
with:
# One stable disk for the whole repository. The v1 per-PR/per-config
@@ -2327,7 +2328,7 @@ jobs:
commit: "false"
- name: Restore extension boundary artifacts from sticky disk
if: matrix.task == 'lint' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
if: matrix.task == 'lint' && vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
shell: bash
env:
# Config/toolchain inputs the tree-OID gate below cannot see; must
@@ -2544,7 +2545,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -2610,8 +2611,8 @@ jobs:
install-bun: "false"
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
# Same-repo runs carry boundary artifacts on a Blacksmith sticky disk:
# the GitHub cache below is evicted so quickly under the repo quota that
@@ -2619,7 +2620,7 @@ jobs:
# plugin-sdk declarations. Fork PRs must never produce writable
# repository-global snapshots, so they keep the GitHub cache path.
- name: Mount extension boundary sticky disk
if: matrix.group == 'extension-package-boundary' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
if: matrix.group == 'extension-package-boundary' && vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
uses: useblacksmith/stickydisk@6d373c96a74cbde0c99fedc5ea5d3a7ba66ba494 # main (post-v1.4.0 hot-attach fix)
with:
# One stable disk for the whole repository. The v1 per-PR/per-config
@@ -2640,7 +2641,7 @@ jobs:
- name: Restore extension boundary artifacts from sticky disk
id: boundary-sticky-restore
if: matrix.group == 'extension-package-boundary' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
if: matrix.group == 'extension-package-boundary' && vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
shell: bash
env:
# Config/toolchain inputs the tree-OID gate below cannot see; must
@@ -2861,7 +2862,7 @@ jobs:
# clone). Seed the payload before the sticky post-action flushes.
# rsync -aR mirrors the repo-relative layout the restore step replays.
- name: Seed extension boundary sticky disk
if: success() && steps.boundary-sticky-restore.outputs.restored == 'false' && matrix.group == 'extension-package-boundary' && github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request' && github.repository == 'openclaw/openclaw'
if: success() && steps.boundary-sticky-restore.outputs.restored == 'false' && matrix.group == 'extension-package-boundary' && vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request' && github.repository == 'openclaw/openclaw'
shell: bash
env:
# Must stay identical to the restore gates above.
@@ -2889,7 +2890,7 @@ jobs:
contents: read
needs: [preflight]
if: needs.preflight.outputs.run_check_docs == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
steps:
- *linux_node_checkout_step
@@ -2899,8 +2900,8 @@ jobs:
install-bun: "false"
# Same-repo Blacksmith runs restore the dependency cache published by
# preflight; hosted paths use the pnpm store cache instead.
dependency-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
dependency-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false' }}
use-actions-cache: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true' }}
- name: Check formatting
if: needs.preflight.outputs.run_format_check == 'true'
@@ -2958,7 +2959,7 @@ jobs:
contents: read
needs: [preflight]
if: needs.preflight.outputs.run_skills_python_job == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
steps:
- name: Checkout
@@ -3013,7 +3014,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_windows == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'windows-2025' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && (matrix.runner || 'blacksmith-8vcpu-windows-2025') || 'windows-2025') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'windows-2025' || github.event_name == 'workflow_dispatch' && 'windows-2025' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && (matrix.runner || 'blacksmith-8vcpu-windows-2025') || 'windows-2025') }}
timeout-minutes: 60
env:
NODE_OPTIONS: --max-old-space-size=8192
@@ -3159,7 +3160,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_macos_node == 'true' }}
runs-on: ${{ (github.event_name == 'workflow_dispatch' || github.run_attempt > 1) && 'macos-15' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-6vcpu-macos-15' || 'macos-15') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'macos-15' || (github.event_name == 'workflow_dispatch' || github.run_attempt > 1) && 'macos-15' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-6vcpu-macos-15' || 'macos-15') }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -3198,10 +3199,10 @@ jobs:
name: "macos-swift"
needs: [preflight]
if: needs.preflight.outputs.run_macos_swift == 'true'
runs-on: ${{ (github.event_name == 'workflow_dispatch' || github.run_attempt > 1) && 'macos-26' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-12vcpu-macos-26' || 'macos-26') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'macos-26' || (github.event_name == 'workflow_dispatch' || github.run_attempt > 1) && 'macos-26' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-12vcpu-macos-26' || 'macos-26') }}
# Hosted runs may still be finalizing large Swift caches after every test
# passes; keep that post-job work non-blocking.
timeout-minutes: ${{ (github.event_name == 'workflow_dispatch' || github.run_attempt > 1) && 30 || 20 }}
timeout-minutes: ${{ (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || github.event_name == 'workflow_dispatch' || github.run_attempt > 1) && 30 || 20 }}
env:
HISTORICAL_TARGET: ${{ needs.preflight.outputs.compatibility_target }}
SWIFT_TEST_EXECUTION: ${{ (github.event_name == 'workflow_dispatch' || github.run_attempt > 1) && 'serial' || 'parallel' }}
@@ -3417,7 +3418,7 @@ jobs:
name: "ios-build"
needs: [preflight]
if: needs.preflight.outputs.run_ios_build == 'true'
runs-on: ${{ (github.event_name == 'workflow_dispatch' || github.run_attempt > 1) && 'macos-26' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-12vcpu-macos-26' || 'macos-26') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'macos-26' || (github.event_name == 'workflow_dispatch' || github.run_attempt > 1) && 'macos-26' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-12vcpu-macos-26' || 'macos-26') }}
timeout-minutes: 150
env:
HISTORICAL_TARGET: ${{ needs.preflight.outputs.compatibility_target }}
@@ -3598,7 +3599,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_android_job == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
fail-fast: false
@@ -3672,11 +3673,11 @@ jobs:
# build caches) on a Blacksmith sticky disk: the setup-java gradle cache
# above is evicted so quickly under the repo quota that it rarely
# restores, leaving every run to re-resolve the full dependency graph.
# Fork PRs and manual dispatches must never produce writable
# Fork PRs, manual dispatches, and the GitHub backend must never produce writable
# repository-global snapshots, so they keep the GitHub cache path (their
# runs-on already selects non-Blacksmith ubuntu-24.04 runners).
- name: Mount Gradle sticky disk
if: github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
if: vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
uses: useblacksmith/stickydisk@6d373c96a74cbde0c99fedc5ea5d3a7ba66ba494 # main (post-v1.4.0 hot-attach fix)
with:
# One stable disk per matrix task. The v1 per-PR/per-dependency-hash
@@ -3698,7 +3699,7 @@ jobs:
commit: ${{ github.event_name != 'pull_request' && 'true' || 'false' }}
- name: Point Gradle at the sticky disk
if: github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
if: vars.OPENCLAW_CI_RUNNER_BACKEND != 'github' && github.event_name != 'workflow_dispatch' && github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw')
shell: bash
env:
# Gradle caches are content-addressed, so a stale snapshot is safe
@@ -3731,6 +3732,7 @@ jobs:
- name: Run Android ${{ matrix.task }}
working-directory: apps/android
env:
CI_RUNNER_BACKEND: ${{ vars.OPENCLAW_CI_RUNNER_BACKEND }}
TASK: ${{ matrix.task }}
shell: bash
run: |
@@ -3753,7 +3755,7 @@ jobs:
./gradlew --no-daemon --build-cache :wear:testDebugUnitTest
;;
build-play)
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
if [ "$CI_RUNNER_BACKEND" = "github" ] || [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then
# GitHub-hosted runners have less memory headroom. Separate Gradle
# processes release each variant's build state before the next starts.
./gradlew --no-daemon --build-cache \
+19 -1
View File
@@ -202,6 +202,24 @@ for commands and recovery.
| `blacksmith-6vcpu-macos-15` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-15` |
| `blacksmith-12vcpu-macos-26` | `macos-swift` and `ios-build` on `openclaw/openclaw`; forks fall back to `macos-26` |
### Blacksmith outage circuit breaker
The repository variable `OPENCLAW_CI_RUNNER_BACKEND` controls the runner backend for `ci.yml`. Leave it unset or set it to `blacksmith` for the normal Blacksmith-first routing. During a Blacksmith outage, set it to `github` to send every configurable CI job to that job's existing GitHub-hosted fallback label:
```bash
gh variable set OPENCLAW_CI_RUNNER_BACKEND --repo openclaw/openclaw --body github
```
Degraded mode uses the same hosted paths exercised by manual dispatches and fork pull requests. Blacksmith-only Docker and sticky-disk steps are skipped, dependency setup uses the ordinary Actions pnpm-store cache, and low-memory Android builds use separate Gradle processes. Expect slower builds and test lanes on standard 4-core hosted runners. Blacksmith's runner-registration budget is irrelevant while the breaker is active, but GitHub-hosted concurrency limits apply.
Flip back after the outage by deleting the variable, which restores the default behavior:
```bash
gh variable delete OPENCLAW_CI_RUNNER_BACKEND --repo openclaw/openclaw
```
Scheduled health detection and automatic flipping are an explicit follow-up; `ci.yml` does not probe Blacksmith or mutate this variable.
## Runner registration budget
OpenClaw's current GitHub runner-registration bucket reports 10,000 self-hosted
@@ -222,7 +240,7 @@ concurrent repositories, retries, and burst overlap.
The changed-target PR plan reduces the common Node test burst from 14 Blacksmith registrations to one. Broad-risk PRs keep the 14-registration compact fallback, so the worst case does not increase.
Canonical-repo CI keeps Blacksmith as the default runner path for pushes and first-attempt same-repo pull-request runs. Pull-request retries of both UI E2E jobs use GitHub-hosted Ubuntu; push retries stay on Blacksmith. All `workflow_dispatch` runs, including `release_gate`, and non-canonical repository runs use GitHub-hosted runners. Normal canonical runs do not currently probe Blacksmith queue health or automatically fall back to GitHub-hosted labels when Blacksmith is unavailable.
Canonical-repo CI keeps Blacksmith as the default runner path for pushes and first-attempt same-repo pull-request runs. Pull-request retries of both UI E2E jobs use GitHub-hosted Ubuntu; push retries stay on Blacksmith. All `workflow_dispatch` runs, including `release_gate`, and non-canonical repository runs use GitHub-hosted runners. The [Blacksmith outage circuit breaker](#blacksmith-outage-circuit-breaker) provides a manual repository-wide fallback; canonical runs do not probe Blacksmith queue health or flip it automatically.
## Surface ratchets
+168 -7
View File
@@ -80,7 +80,9 @@ function evaluateWorkflowExpression(
context: {
eventName: "pull_request" | "push" | "workflow_dispatch";
headRepository?: string;
matrix?: Record<string, unknown>;
repository: string;
runnerBackend?: "" | "blacksmith" | "github";
runAttempt: number;
},
) {
@@ -108,6 +110,10 @@ function evaluateWorkflowExpression(
}
: {},
},
matrix: context.matrix ?? {},
vars: {
OPENCLAW_CI_RUNNER_BACKEND: context.runnerBackend ?? "",
},
});
}
@@ -2753,9 +2759,9 @@ NODE
);
const buildPlayBranches = expectDefined(
buildPlayCase.match(
/if \[ "\$GITHUB_EVENT_NAME" = "workflow_dispatch" \]; then\n([\s\S]*?)\n\s*else\n([\s\S]*?)\n\s*fi/u,
/if \[ "\$CI_RUNNER_BACKEND" = "github" \] \|\| \[ "\$GITHUB_EVENT_NAME" = "workflow_dispatch" \]; then\n([\s\S]*?)\n\s*else\n([\s\S]*?)\n\s*fi/u,
),
"Android build-play event branches",
"Android build-play runner branches",
);
const dispatchBuild = expectDefined(buildPlayBranches[1], "hosted dispatch build branch");
const blacksmithBuild = expectDefined(buildPlayBranches[2], "Blacksmith build branch");
@@ -2772,8 +2778,9 @@ NODE
expect(source).toContain('task: useCompatibleAndroidCi ? "build-play-compat" : "build-play"');
expect(androidJob.name).toBe("${{ matrix.check_name }}");
expect(androidJob["runs-on"]).toBe(
"${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}",
"${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}",
);
expect(runStep.env.CI_RUNNER_BACKEND).toBe("${{ vars.OPENCLAW_CI_RUNNER_BACKEND }}");
expect(runStep.run).toContain(":app:testPlayDebugUnitTest");
expect(runStep.run).toContain(":app:testThirdPartyDebugUnitTest");
expect(dispatchBuild.match(/^\s*\.\/gradlew\b/gmu)).toHaveLength(3);
@@ -2843,6 +2850,110 @@ NODE
expect(workflow.jobs["pnpm-store-warmup"]["runs-on"]).toContain("blacksmith-4vcpu-ubuntu-2404");
});
it("routes every configurable CI job to GitHub-hosted runners when the backend breaker is set", () => {
const workflow = readCiWorkflow();
const jobs = workflow.jobs as Record<string, { "runs-on": unknown }>;
const expectedHostedRunners = {
android: "ubuntu-24.04",
"build-artifacts": "ubuntu-24.04",
"check-additional-shard": "ubuntu-24.04",
"check-docs": "ubuntu-24.04",
"check-shard": "ubuntu-24.04",
"checks-fast-channel-contracts-shard": "ubuntu-24.04",
"checks-fast-core": "ubuntu-24.04",
"checks-fast-plugin-contracts-shard": "ubuntu-24.04",
"checks-node-compat": "ubuntu-24.04",
"checks-node-core-test-nondist-shard": "ubuntu-24.04",
"checks-ui": "ubuntu-24.04",
"checks-ui-e2e": "ubuntu-24.04",
"checks-ui-e2e-real-gateway": "ubuntu-24.04",
"control-ui-i18n": "ubuntu-24.04",
"ios-build": "macos-26",
"macos-node": "macos-15",
"macos-swift": "macos-26",
"native-i18n": "ubuntu-24.04",
"pnpm-store-warmup": "ubuntu-24.04",
preflight: "ubuntu-24.04",
"qa-smoke-ci-profile": "ubuntu-24.04",
"skills-python": "ubuntu-24.04",
"sqlite-session-lifecycle": "ubuntu-24.04",
"checks-windows": "windows-2025",
} as const;
const configurableJobs = Object.entries(jobs)
.filter(([, job]) => String(job["runs-on"]).startsWith("${{"))
.map(([jobName]) => jobName)
.toSorted();
const canonicalPullRequest = {
eventName: "pull_request",
headRepository: "openclaw/openclaw",
matrix: { runner: "blacksmith-32vcpu-ubuntu-2404" },
repository: "openclaw/openclaw",
runAttempt: 1,
} as const;
expect(configurableJobs).toEqual(Object.keys(expectedHostedRunners).toSorted());
for (const [jobName, hostedRunner] of Object.entries(expectedHostedRunners)) {
const expression = jobs[jobName]?.["runs-on"];
expect(expression, jobName).toContain("vars.OPENCLAW_CI_RUNNER_BACKEND == 'github'");
expect(
evaluateWorkflowExpression(expression, {
...canonicalPullRequest,
runnerBackend: "github",
}),
jobName,
).toBe(hostedRunner);
expect(
evaluateWorkflowExpression(expression, {
...canonicalPullRequest,
runnerBackend: "blacksmith",
}),
jobName,
).toBe(evaluateWorkflowExpression(expression, canonicalPullRequest));
}
});
it("gives breaker-routed hosted jobs their hosted timeout budgets", () => {
const workflow = readCiWorkflow();
const jobs = workflow.jobs as Record<string, { "timeout-minutes": unknown }>;
const expectedHostedTimeouts = {
"build-artifacts": 35,
"macos-swift": 30,
} as const;
const routeDependentTimeoutJobs = Object.entries(jobs)
.filter(([, job]) => {
const timeout = job["timeout-minutes"];
return typeof timeout === "string" && timeout.includes("github.");
})
.map(([jobName]) => jobName)
.toSorted();
const canonicalPullRequest = {
eventName: "pull_request",
headRepository: "openclaw/openclaw",
repository: "openclaw/openclaw",
runAttempt: 1,
} as const;
expect(routeDependentTimeoutJobs).toEqual(Object.keys(expectedHostedTimeouts).toSorted());
for (const [jobName, hostedTimeout] of Object.entries(expectedHostedTimeouts)) {
const expression = jobs[jobName]?.["timeout-minutes"];
expect(expression, jobName).toContain("vars.OPENCLAW_CI_RUNNER_BACKEND == 'github'");
expect(
evaluateWorkflowExpression(expression, {
...canonicalPullRequest,
runnerBackend: "github",
}),
jobName,
).toBe(hostedTimeout);
expect(
evaluateWorkflowExpression(expression, {
...canonicalPullRequest,
runnerBackend: "blacksmith",
}),
jobName,
).toBe(20);
}
});
it("scans only the pull request commit range for leaked credentials", () => {
const securitySteps = readCiWorkflow().jobs["security-fast"].steps as WorkflowStep[];
const fetchScanHistoryIndex = securitySteps.findIndex(
@@ -2995,6 +3106,10 @@ NODE
});
expect(writers[0]?.step.if).toContain("github.ref == 'refs/heads/main'");
expect(writers[0]?.step.if).toContain("github.event_name == 'pull_request'");
expect(writers[0]?.step.if).toContain("vars.OPENCLAW_CI_RUNNER_BACKEND != 'github'");
expect(workflow.jobs["pnpm-store-warmup"].if).toContain(
"vars.OPENCLAW_CI_RUNNER_BACKEND == 'github'",
);
const consumers = dependencySetups.filter(({ jobName }) => jobName !== "preflight");
expect(consumers.map(({ jobName }) => jobName).toSorted()).toEqual([
"build-artifacts",
@@ -3019,6 +3134,29 @@ NODE
expect(consumer.with, jobName).not.toHaveProperty("save-dependency-cache");
expect(consumer.with?.["dependency-cache"], jobName).toContain("'true' || 'false'");
expect(consumer.with?.["use-actions-cache"], jobName).toContain("'false' || 'true'");
expect(consumer.with?.["dependency-cache"], jobName).toContain(
"vars.OPENCLAW_CI_RUNNER_BACKEND",
);
expect(
evaluateWorkflowExpression(consumer.with?.["dependency-cache"], {
eventName: "push",
matrix: { node_version: "24.x" },
repository: "openclaw/openclaw",
runnerBackend: "github",
runAttempt: 1,
}),
jobName,
).toBe("false");
expect(
evaluateWorkflowExpression(consumer.with?.["use-actions-cache"], {
eventName: "push",
matrix: { node_version: "24.x" },
repository: "openclaw/openclaw",
runnerBackend: "github",
runAttempt: 1,
}),
jobName,
).toBe("true");
}
for (const { jobName, step: setup } of Object.entries(workflow.jobs).flatMap(([jobName, job]) =>
((job as { steps?: WorkflowStep[] }).steps ?? [])
@@ -3638,7 +3776,7 @@ server.listen(0, "127.0.0.1", () => writeFileSync(readyPath, String(server.addre
expect(source).toContain("createNodeTestShardBundles");
expect(workflow.jobs["build-artifacts"]["runs-on"]).toContain("blacksmith-32vcpu-ubuntu-2404");
expect(workflow.jobs["build-artifacts"]["timeout-minutes"]).toBe(
"${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 35 || 20 }}",
"${{ (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository)) && 35 || 20 }}",
);
expect(buildArtifactsTestbox.jobs["build-artifacts"]["runs-on"]).toBe(
"blacksmith-16vcpu-ubuntu-2404",
@@ -3705,6 +3843,9 @@ server.listen(0, "127.0.0.1", () => writeFileSync(readyPath, String(server.addre
);
expect(boundaryMount.with.key).toBe("${{ github.repository }}-ext-boundary-v2");
expect(lintMount.with.key).toBe(boundaryMount.with.key);
for (const gate of [boundaryMount, lintMount]) {
expect(gate.if).toContain("vars.OPENCLAW_CI_RUNNER_BACKEND != 'github'");
}
// Single semantic writer: protected pushes commit explicitly (not
// on-change/if-missing, whose allocated-byte heuristic can strand a stale
// marker); PR clones and the lint consumer stay read-only.
@@ -3731,6 +3872,7 @@ server.listen(0, "127.0.0.1", () => writeFileSync(readyPath, String(server.addre
expect(lintRestoreStep.env.BOUNDARY_CONFIG_HASH).toBe(configHash);
for (const gate of [restoreStep, lintRestoreStep, seedStep]) {
expect(gate.run).toContain('echo "$BOUNDARY_CONFIG_HASH"');
expect(gate.if).toContain("vars.OPENCLAW_CI_RUNNER_BACKEND != 'github'");
}
// Seeding is writer-only work: PR mounts never commit, so seeding there
// would burn wall clock on a discarded clone.
@@ -3756,6 +3898,10 @@ server.listen(0, "127.0.0.1", () => writeFileSync(readyPath, String(server.addre
// minted a backing disk per PR/bump until Blacksmith's installation-wide
// budget 429-failed every mount fleet-wide.
expect(mountWith.key).toBe("${{ github.repository }}-gradle-v2-${{ matrix.task }}");
expect(androidSteps.find((step) => step.name === "Mount Gradle sticky disk")?.if).toContain(
"vars.OPENCLAW_CI_RUNNER_BACKEND != 'github'",
);
expect(pointStep.if).toContain("vars.OPENCLAW_CI_RUNNER_BACKEND != 'github'");
// Single semantic writer: protected pushes commit explicitly (on-change's
// allocated-byte heuristic can miss a same-size refresh and strand the
// fingerprint marker); PR clones stay read-only.
@@ -5579,9 +5725,9 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
"node-version": "24.x",
"install-bun": "false",
"dependency-cache":
"${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'false' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false') }}",
"${{ (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'false' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'true' || 'false') }}",
"use-actions-cache":
"${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'true' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true') }}",
"${{ (vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'true' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && 'false' || 'true') }}",
} as const;
expect(uiE2eSetup.with).toEqual(expectedUiE2eSetup);
const realGatewaySetup = expectDefined(
@@ -5615,6 +5761,17 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
},
expected: { blacksmith: true, dependencyCache: "true", useActionsCache: "false" },
},
{
name: "same-repo pull request with GitHub backend",
context: {
eventName: "pull_request",
headRepository: "openclaw/openclaw",
repository: "openclaw/openclaw",
runnerBackend: "github",
runAttempt: 1,
},
expected: { blacksmith: false, dependencyCache: "false", useActionsCache: "true" },
},
{
name: "same-repo pull request retry",
context: {
@@ -5656,7 +5813,7 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
] as const;
for (const { blacksmithRunner, job, name: jobName, setup } of routedUiE2eJobs) {
expect(job["runs-on"]).toBe(
"${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && '" +
"${{ vars.OPENCLAW_CI_RUNNER_BACKEND == 'github' && 'ubuntu-24.04' || (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.run_attempt > 1)) && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'openclaw/openclaw') && '" +
blacksmithRunner +
"' || 'ubuntu-24.04') }}",
);
@@ -7413,6 +7570,7 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
"useblacksmith/setup-docker-builder@6ff44f8e5255f9d8aa31ef22f7e57a2d926b7da0",
);
expect(smokeDockerCacheStep.if).toContain("matrix.docker_cache == true");
expect(smokeDockerCacheStep.if).toContain("vars.OPENCLAW_CI_RUNNER_BACKEND != 'github'");
expect(smokeDockerCacheStep.if).toContain("github.event_name != 'workflow_dispatch'");
expect(smokeDockerCacheStep.if).toContain("github.repository == 'openclaw/openclaw'");
expect(smokeDockerCacheStep.if).toContain(
@@ -7450,6 +7608,9 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
expect(smokeRunStep.env.OPENCLAW_QA_SUITE_WORKER_START_STAGGER_MS).toContain(
"github.event_name != 'workflow_dispatch'",
);
expect(smokeRunStep.env.OPENCLAW_QA_SUITE_WORKER_START_STAGGER_MS).toContain(
"vars.OPENCLAW_CI_RUNNER_BACKEND != 'github'",
);
expect(smokeRunStep.env.OPENCLAW_QA_SUITE_WORKER_START_STAGGER_MS).toContain(
"github.repository == 'openclaw/openclaw'",
);