From e4fe99987d9a13748ce2223f894f260f84584586 Mon Sep 17 00:00:00 2001 From: ethernet Date: Fri, 17 Jul 2026 16:40:28 -0400 Subject: [PATCH] feat(ci): migrate all workflows to GKE self-hosted runners Swap all `runs-on: ubuntu-latest` to `runs-on: arc-runner-set` all jobs. The ARM docker build job in docker.yml uses `${{ matrix.runner }}` and is left untouched since the GKE runner pool is x86_64 only. Runners are backed by ARC (Actions Runner Controller) on a GKE cluster with a spot preemptible node pool that scales based on job demand. Use the baked Electron dependencies for the desktop E2E job. --- .github/workflows/ci.yml | 8 ++++---- .github/workflows/contributor-check.yml | 2 +- .github/workflows/deploy-site.yml | 4 ++-- .github/workflows/docker-lint.yml | 4 ++-- .github/workflows/docker.yml | 10 +++++----- .github/workflows/docs-site-checks.yml | 4 ++-- .github/workflows/e2e-desktop.yml | 15 ++++----------- .github/workflows/history-check.yml | 2 +- .github/workflows/infographic-check.yml | 2 +- .github/workflows/js-autofix.yml | 4 ++-- .github/workflows/js-tests.yml | 4 ++-- .github/workflows/label-rerun.yml | 2 +- .github/workflows/lint.yml | 10 +++++----- .github/workflows/lockfile-diff.yml | 2 +- .github/workflows/osv-scanner.yml | 2 +- .github/workflows/publish-e2e-evidence.yml | 2 +- .github/workflows/review-labels.yml | 2 +- .github/workflows/skills-index-freshness.yml | 2 +- .github/workflows/skills-index.yml | 6 +++--- .github/workflows/supply-chain-audit.yml | 6 +++--- .github/workflows/tests.yml | 8 ++++---- .github/workflows/uv-lockfile-check.yml | 2 +- 22 files changed, 48 insertions(+), 55 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3441d7bb6db..33f0fedae99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: # ───────────────────────────────────────────────────────────────────── detect: name: Detect affected areas - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 outputs: python: ${{ steps.classify.outputs.python }} @@ -181,7 +181,7 @@ jobs: name: CI review comment (live) needs: [detect, review-labels, lockfile-diff, supply-chain, osv-scanner, uv-lockfile, history-check, contributor-check, e2e-desktop] if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork != true - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 40 steps: - name: Checkout code @@ -274,7 +274,7 @@ jobs: # we don't require docker to pass rn because it's so slow lol # - docker if: always() - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 outputs: needs-json: ${{ steps.evaluate.outputs.needs-json }} @@ -316,7 +316,7 @@ jobs: name: CI timing report needs: [all-checks-pass, docker] if: always() - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 steps: - name: Checkout code diff --git a/.github/workflows/contributor-check.yml b/.github/workflows/contributor-check.yml index 014e1e2ff93..ae83d73b4bb 100644 --- a/.github/workflows/contributor-check.yml +++ b/.github/workflows/contributor-check.yml @@ -12,7 +12,7 @@ permissions: jobs: check-attribution: - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 outputs: review_status: ${{ steps.check-emails.outputs.review_status }} diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index 3ac2c4741f8..e44a12301ef 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -40,7 +40,7 @@ jobs: # a skills-index PR that doesn't touch website/** paths and so # doesn't auto-deploy via the deploy-docs path. if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 30 steps: - name: Trigger Vercel Deploy @@ -48,7 +48,7 @@ jobs: deploy-docs: if: github.repository == 'NousResearch/hermes-agent' - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 30 environment: name: github-pages diff --git a/.github/workflows/docker-lint.yml b/.github/workflows/docker-lint.yml index 89b80fa10e0..fd4e4cae0d5 100644 --- a/.github/workflows/docker-lint.yml +++ b/.github/workflows/docker-lint.yml @@ -23,7 +23,7 @@ concurrency: jobs: hadolint: name: Lint Dockerfile (hadolint) - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 5 steps: - name: Checkout code @@ -38,7 +38,7 @@ jobs: shellcheck: name: Lint docker/ shell scripts (shellcheck) - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 5 steps: - name: Checkout code diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ed89185acfc..b1d89b904ec 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -37,12 +37,12 @@ jobs: matrix: include: - arch: amd64 - runner: ubuntu-latest + runner: arc-runner-set platform: linux/amd64 cache-from: type=gha,scope=docker-amd64 cache-to: type=gha,mode=max,scope=docker-amd64 - arch: arm64 - runner: ubuntu-24.04-arm + runner: arc-runner-arm64 platform: linux/arm64 cache-from: type=gha,scope=docker-arm64 cache-to: type=gha,mode=max,scope=docker-arm64 @@ -143,12 +143,12 @@ jobs: matrix: include: - arch: amd64 - runner: ubuntu-latest + runner: arc-runner-set platform: linux/amd64 cache-from: type=gha,scope=docker-amd64 cache-to: type=gha,mode=max,scope=docker-amd64 - arch: arm64 - runner: ubuntu-24.04-arm + runner: arc-runner-arm64 platform: linux/arm64 cache-from: type=gha,scope=docker-arm64 cache-to: type=gha,mode=max,scope=docker-arm64 @@ -216,7 +216,7 @@ jobs: # --------------------------------------------------------------------------- merge: if: github.repository == 'NousResearch/hermes-agent' && (github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'release') - runs-on: ubuntu-latest + runs-on: arc-runner-set needs: [publish] timeout-minutes: 10 environment: container-publish diff --git a/.github/workflows/docs-site-checks.yml b/.github/workflows/docs-site-checks.yml index 41acf1790f4..816c286909a 100644 --- a/.github/workflows/docs-site-checks.yml +++ b/.github/workflows/docs-site-checks.yml @@ -8,7 +8,7 @@ permissions: jobs: docs-site-checks: - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 20 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -32,7 +32,7 @@ jobs: - name: Install ascii-guard uses: ./.github/actions/retry with: - command: python -m pip install ascii-guard==2.3.0 pyyaml==6.0.3 + command: python3 -m pip install ascii-guard==2.3.0 pyyaml==6.0.3 - name: Extract skill metadata for dashboard run: python3 website/scripts/extract-skills.py diff --git a/.github/workflows/e2e-desktop.yml b/.github/workflows/e2e-desktop.yml index bdaaa6d6cbb..011de6c7ad1 100644 --- a/.github/workflows/e2e-desktop.yml +++ b/.github/workflows/e2e-desktop.yml @@ -17,7 +17,7 @@ concurrency: jobs: e2e: name: Playwright E2E (Linux) - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 20 outputs: review_status: ${{ steps.review-status.outputs.review_status }} @@ -25,16 +25,9 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # ── System deps for Electron on headless Ubuntu ─────────────────── - # Electron needs GTK, NSS,atk, etc. even under xvfb. Playwright's - # install-deps covers browsers; for Electron we install the apt - # packages directly. - - name: Install system dependencies for Electron - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq \ - xvfb \ - libgtk-3-0 libnotify4 libnss3 libxss1 libxtst6 \ - xdg-utils libatspi2.0-0 libdrm2 libgbm1 libasound2t64 + # xvfb and Electron's GTK/NSS/audio libraries are baked into + # nousresearch/nous-gke-runner so this works in the ARC runner pod + # without requiring passwordless sudo. # ── Node ─────────────────────────────────────────────────────────── - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 diff --git a/.github/workflows/history-check.yml b/.github/workflows/history-check.yml index 668f0f795dd..cf513012abe 100644 --- a/.github/workflows/history-check.yml +++ b/.github/workflows/history-check.yml @@ -25,7 +25,7 @@ permissions: jobs: check-common-ancestor: - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 outputs: review_status: ${{ steps.merge-base-check.outputs.review_status }} diff --git a/.github/workflows/infographic-check.yml b/.github/workflows/infographic-check.yml index 288f6f493a0..34974a38ccb 100644 --- a/.github/workflows/infographic-check.yml +++ b/.github/workflows/infographic-check.yml @@ -28,7 +28,7 @@ permissions: jobs: check-no-committed-infographics: - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 outputs: review_status: ${{ steps.infographic-check.outputs.review_status }} diff --git a/.github/workflows/js-autofix.yml b/.github/workflows/js-autofix.yml index 2dfbe7e0b17..151966359b2 100644 --- a/.github/workflows/js-autofix.yml +++ b/.github/workflows/js-autofix.yml @@ -57,7 +57,7 @@ concurrency: jobs: generate-patch: name: Generate eslint --fix patch - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 5 outputs: has-fixes: ${{ steps.produce-patch.outputs.has-fixes }} @@ -120,7 +120,7 @@ jobs: # Skip entirely when generate-patch found no fixes — saves a runner, # avoids a redundant checkout/download, and keeps the job graph honest. if: needs.generate-patch.outputs.has-fixes == 'true' - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 15 environment: trusted-automation permissions: diff --git a/.github/workflows/js-tests.yml b/.github/workflows/js-tests.yml index 25786b1c95b..b7815457195 100644 --- a/.github/workflows/js-tests.yml +++ b/.github/workflows/js-tests.yml @@ -7,7 +7,7 @@ on: jobs: workspaces: name: List npm workspaces - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 20 outputs: checks: ${{ steps.set-matrix.outputs.checks }} @@ -51,7 +51,7 @@ jobs: check: name: ${{ matrix.package }} / ${{ matrix.script }} needs: workspaces - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 20 strategy: matrix: diff --git a/.github/workflows/label-rerun.yml b/.github/workflows/label-rerun.yml index fbd8b3b8932..ea2bdd5323e 100644 --- a/.github/workflows/label-rerun.yml +++ b/.github/workflows/label-rerun.yml @@ -26,7 +26,7 @@ jobs: rerun-review-labels: name: Rerun review-labels job if: github.event.label.name == 'ci-reviewed' - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 40 steps: - name: Wait for CI run to finish, then rerun failed jobs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3ae120f71f3..7e5000680cb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,7 +30,7 @@ jobs: lint-diff: name: ruff + ty diff if: inputs.event_name == 'pull_request' - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 steps: - name: Checkout code @@ -108,7 +108,7 @@ jobs: env: HEAD_REF: ${{ inputs.event_name == 'pull_request' && github.head_ref || github.ref_name }} run: | - python scripts/lint_diff.py \ + python3 scripts/lint_diff.py \ --base-ruff .lint-reports/base/ruff.json \ --head-ruff .lint-reports/head/ruff.json \ --base-ty .lint-reports/base/ty.json \ @@ -126,7 +126,7 @@ jobs: # ``lint-diff`` job above runs independently so reviewers still get # the diff comment even when enforcement fails. name: ruff enforcement (blocking) - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 5 steps: - name: Checkout code @@ -157,7 +157,7 @@ jobs: # shebang scripts via subprocess, bare open() without encoding=, etc. # See scripts/check-windows-footguns.py for the full rule list. name: Windows footguns (blocking) - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 5 steps: - name: Checkout code @@ -169,4 +169,4 @@ jobs: python-version: "3.11" - name: Run footgun checker - run: python scripts/check-windows-footguns.py --all + run: python3 scripts/check-windows-footguns.py --all diff --git a/.github/workflows/lockfile-diff.yml b/.github/workflows/lockfile-diff.yml index 9d8d59f6da7..35b56762351 100644 --- a/.github/workflows/lockfile-diff.yml +++ b/.github/workflows/lockfile-diff.yml @@ -34,7 +34,7 @@ concurrency: jobs: diff: name: package-lock.json semantic diff - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 5 outputs: changed: ${{ steps.diff.outputs.changed }} diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml index 455ede33dd5..f90759ea875 100644 --- a/.github/workflows/osv-scanner.yml +++ b/.github/workflows/osv-scanner.yml @@ -55,7 +55,7 @@ jobs: emit-status: name: Emit review status - runs-on: ubuntu-latest + runs-on: arc-runner-set needs: scan if: always() outputs: diff --git a/.github/workflows/publish-e2e-evidence.yml b/.github/workflows/publish-e2e-evidence.yml index 3d7fe32d780..d8bf4031b1a 100644 --- a/.github/workflows/publish-e2e-evidence.yml +++ b/.github/workflows/publish-e2e-evidence.yml @@ -21,7 +21,7 @@ jobs: publish: name: Publish inline E2E evidence if: github.event.workflow_run.event == 'pull_request' - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 environment: gh-image steps: diff --git a/.github/workflows/review-labels.yml b/.github/workflows/review-labels.yml index c8ea37dbbca..cd8901457d4 100644 --- a/.github/workflows/review-labels.yml +++ b/.github/workflows/review-labels.yml @@ -51,7 +51,7 @@ jobs: check: name: Review label gate if: inputs.ci_review || inputs.mcp_catalog || inputs.supply_chain - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 2 outputs: ci_reviewed: ${{ steps.label-check.outputs.ci_reviewed }} diff --git a/.github/workflows/skills-index-freshness.yml b/.github/workflows/skills-index-freshness.yml index 9e4b2767be5..2cb7ebc3661 100644 --- a/.github/workflows/skills-index-freshness.yml +++ b/.github/workflows/skills-index-freshness.yml @@ -19,7 +19,7 @@ permissions: jobs: check-freshness: if: github.repository == 'NousResearch/hermes-agent' - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 environment: trusted-automation steps: diff --git a/.github/workflows/skills-index.yml b/.github/workflows/skills-index.yml index 5415499e024..c3bd69f018f 100644 --- a/.github/workflows/skills-index.yml +++ b/.github/workflows/skills-index.yml @@ -19,7 +19,7 @@ jobs: build-index: # Only run on the upstream repository, not on forks if: github.repository == 'NousResearch/hermes-agent' - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 15 environment: trusted-automation steps: @@ -44,7 +44,7 @@ jobs: - name: Build skills index env: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - run: python scripts/build_skills_index.py + run: python3 scripts/build_skills_index.py - name: Upload index artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 @@ -59,7 +59,7 @@ jobs: trigger-deploy: needs: build-index if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 15 environment: trusted-automation steps: diff --git a/.github/workflows/supply-chain-audit.yml b/.github/workflows/supply-chain-audit.yml index cca61e03a45..c536b1185f4 100644 --- a/.github/workflows/supply-chain-audit.yml +++ b/.github/workflows/supply-chain-audit.yml @@ -54,7 +54,7 @@ jobs: scan: name: Scan PR for critical supply chain risks if: inputs.scan - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 15 outputs: review_status: ${{ steps.emit-status.outputs.review_status }} @@ -180,7 +180,7 @@ jobs: dep-bounds: name: Check PyPI dependency upper bounds if: inputs.deps - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 15 outputs: review_status: ${{ steps.emit-status.outputs.review_status }} @@ -259,7 +259,7 @@ jobs: name: Aggregate review statuses needs: [scan, dep-bounds] if: always() - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 15 outputs: review_status: ${{ steps.merge.outputs.review_status }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 353e86439ea..c5c7ce37aa3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ concurrency: jobs: generate: name: "Generate slices" - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 outputs: matrix: ${{ steps.matrix.outputs.matrix }} @@ -48,7 +48,7 @@ jobs: test: name: Run tests slice ${{ matrix.slice.index }}/${{ inputs.slice_count }} needs: generate - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 30 strategy: fail-fast: false @@ -149,7 +149,7 @@ jobs: save-durations: needs: test if: needs.test.result == 'success' && github.ref == 'refs/heads/main' - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 10 steps: - name: Download all slice durations @@ -179,7 +179,7 @@ jobs: key: test-durations-${{ github.run_id }} e2e: - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 15 steps: - name: Checkout code diff --git a/.github/workflows/uv-lockfile-check.yml b/.github/workflows/uv-lockfile-check.yml index e0ba3ea7d50..4fb733cb5af 100644 --- a/.github/workflows/uv-lockfile-check.yml +++ b/.github/workflows/uv-lockfile-check.yml @@ -60,7 +60,7 @@ concurrency: jobs: check: name: uv lock --check - runs-on: ubuntu-latest + runs-on: arc-runner-set timeout-minutes: 5 outputs: review_status: ${{ steps.verify.outputs.review_status }}