From 818bb02f281e8e52e4def0e59e1c0d823621891f Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 14 May 2026 20:10:50 -0700 Subject: [PATCH] ci: migrate all workflows to Ubicloud (Linux + Windows, 8-core) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch every `runs-on` in this repo to Ubicloud so CI has a single billing surface, consistent capacity, and 4x more cores on the workloads that were previously stuck on free `ubuntu-latest` (2 cores). Windows uses Ubicloud's Windows pool too — `ubicloud-standard-8-windows` — so the queued-forever problem with GitHub's `windows-latest-8-cores` paid larger runner (org-level larger-runner billing not enabled) goes away. Workflows touched (9): - evals.yml, evals-periodic.yml, ci-image.yml — bump default + matrix from `ubicloud-standard-2` to `ubicloud-standard-8`. The one matrix entry that was already on -8 stays. - windows-free-tests.yml — `ubicloud-standard-2-windows` → `ubicloud-standard-8-windows`. - make-pdf-gate.yml — matrix `ubuntu-latest` → `ubicloud-standard-8`. macOS entry preserved; the poppler-install `if: matrix.os` conditional swaps to match the new label. - actionlint.yml, pr-title-sync.yml, skill-docs.yml, version-gate.yml — `ubuntu-latest` → `ubicloud-standard-8`. .github/actionlint.yaml registers all four Ubicloud labels in one place: - ubicloud-standard-2 - ubicloud-standard-8 - ubicloud-standard-2-windows (the v1.38.0.0 windows-free-tests target) - ubicloud-standard-8-windows (this PR's windows-free-tests target) Removed the duplicate `actionlint.yaml` at the repo root that I accidentally created in the prior commit — actionlint only reads `.github/actionlint.yaml`, so the root file was dead weight. CHANGELOG entry updated: a single "all Ubicloud" sentence in the narrative plus a metrics-row covering the runner pool change, and the itemized line expanded to enumerate the 9 affected workflows. The previously-orphaned "Itemized changes" line about just `windows-free-tests.yml` is replaced. Co-Authored-By: Claude Opus 4.7 --- .github/actionlint.yaml | 2 ++ .github/workflows/actionlint.yml | 2 +- .github/workflows/ci-image.yml | 2 +- .github/workflows/evals-periodic.yml | 4 ++-- .github/workflows/evals.yml | 8 ++++---- .github/workflows/make-pdf-gate.yml | 4 ++-- .github/workflows/pr-title-sync.yml | 2 +- .github/workflows/skill-docs.yml | 2 +- .github/workflows/version-gate.yml | 2 +- .github/workflows/windows-free-tests.yml | 10 +++++----- CHANGELOG.md | 7 ++++--- actionlint.yaml | 4 ---- 12 files changed, 24 insertions(+), 25 deletions(-) delete mode 100644 actionlint.yaml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index cdd601c83..16ffbae6b 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -2,3 +2,5 @@ self-hosted-runner: labels: - ubicloud-standard-2 - ubicloud-standard-8 + - ubicloud-standard-2-windows + - ubicloud-standard-8-windows diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 32ae44826..1fb654aa8 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -2,7 +2,7 @@ name: Workflow Lint on: [push, pull_request] jobs: actionlint: - runs-on: ubuntu-latest + runs-on: ubicloud-standard-8 steps: - uses: actions/checkout@v4 - uses: rhysd/actionlint@v1.7.11 diff --git a/.github/workflows/ci-image.yml b/.github/workflows/ci-image.yml index 1ca283ad7..e36092d4c 100644 --- a/.github/workflows/ci-image.yml +++ b/.github/workflows/ci-image.yml @@ -15,7 +15,7 @@ on: jobs: build: - runs-on: ubicloud-standard-2 + runs-on: ubicloud-standard-8 permissions: contents: read packages: write diff --git a/.github/workflows/evals-periodic.yml b/.github/workflows/evals-periodic.yml index c0ca4f3aa..25fd76d01 100644 --- a/.github/workflows/evals-periodic.yml +++ b/.github/workflows/evals-periodic.yml @@ -15,7 +15,7 @@ env: jobs: build-image: - runs-on: ubicloud-standard-2 + runs-on: ubicloud-standard-8 permissions: contents: read packages: write @@ -56,7 +56,7 @@ jobs: ${{ env.IMAGE }}:latest evals: - runs-on: ubicloud-standard-2 + runs-on: ubicloud-standard-8 needs: build-image container: image: ${{ needs.build-image.outputs.image-tag }} diff --git a/.github/workflows/evals.yml b/.github/workflows/evals.yml index ee658aee6..c9aa6a293 100644 --- a/.github/workflows/evals.yml +++ b/.github/workflows/evals.yml @@ -15,7 +15,7 @@ env: jobs: # Build Docker image with pre-baked toolchain (cached — only rebuilds on Dockerfile/lockfile change) build-image: - runs-on: ubicloud-standard-2 + runs-on: ubicloud-standard-8 permissions: contents: read packages: write @@ -56,7 +56,7 @@ jobs: ${{ env.IMAGE }}:latest evals: - runs-on: ${{ matrix.suite.runner || 'ubicloud-standard-2' }} + runs-on: ${{ matrix.suite.runner || 'ubicloud-standard-8' }} needs: build-image container: image: ${{ needs.build-image.outputs.image-tag }} @@ -155,7 +155,7 @@ jobs: retention-days: 90 report: - runs-on: ubicloud-standard-2 + runs-on: ubicloud-standard-8 needs: evals if: always() && github.event_name == 'pull_request' timeout-minutes: 5 @@ -219,7 +219,7 @@ jobs: $(echo -e "$SUITE_LINES") --- - *12x ubicloud-standard-2 (Docker: pre-baked toolchain + deps) | wall clock ≈ slowest suite*" + *12x ubicloud-standard-8 (Docker: pre-baked toolchain + deps) | wall clock ≈ slowest suite*" if [ "$FAILED" -gt 0 ]; then FAILURES="" diff --git a/.github/workflows/make-pdf-gate.yml b/.github/workflows/make-pdf-gate.yml index eab5c4fbe..60d9a1405 100644 --- a/.github/workflows/make-pdf-gate.yml +++ b/.github/workflows/make-pdf-gate.yml @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] + os: [ubicloud-standard-8, macos-latest] # Windows is tolerant-mode — Xpdf / Poppler-Windows extraction # differs enough from the Linux/macOS baseline that the strict # exact-diff gate is unreliable. Enable once the normalized @@ -48,7 +48,7 @@ jobs: run: brew install poppler - name: Install poppler-utils (Ubuntu) - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubicloud-standard-8' run: sudo apt-get update && sudo apt-get install -y poppler-utils - name: Install Playwright Chromium diff --git a/.github/workflows/pr-title-sync.yml b/.github/workflows/pr-title-sync.yml index 7cd274cd4..6f5b3d3e5 100644 --- a/.github/workflows/pr-title-sync.yml +++ b/.github/workflows/pr-title-sync.yml @@ -13,7 +13,7 @@ concurrency: jobs: sync: name: Sync PR title to VERSION - runs-on: ubuntu-latest + runs-on: ubicloud-standard-8 permissions: contents: read pull-requests: write diff --git a/.github/workflows/skill-docs.yml b/.github/workflows/skill-docs.yml index 34ea7f8e9..700a8222a 100644 --- a/.github/workflows/skill-docs.yml +++ b/.github/workflows/skill-docs.yml @@ -2,7 +2,7 @@ name: Skill Docs Freshness on: [push, pull_request] jobs: check-freshness: - runs-on: ubuntu-latest + runs-on: ubicloud-standard-8 steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 diff --git a/.github/workflows/version-gate.yml b/.github/workflows/version-gate.yml index 262baf6ea..2c60d9d76 100644 --- a/.github/workflows/version-gate.yml +++ b/.github/workflows/version-gate.yml @@ -14,7 +14,7 @@ concurrency: jobs: check: name: Check VERSION is not stale vs queue - runs-on: ubuntu-latest + runs-on: ubicloud-standard-8 permissions: contents: read pull-requests: read diff --git a/.github/workflows/windows-free-tests.yml b/.github/workflows/windows-free-tests.yml index 56fcc577d..695fc649f 100644 --- a/.github/workflows/windows-free-tests.yml +++ b/.github/workflows/windows-free-tests.yml @@ -8,10 +8,10 @@ name: Windows Free Tests # targeted resolver tests that exercise the Bun.which-based claude binary # resolution + the GSTACK_CLAUDE_BIN override path on Windows. # -# Runner: Ubicloud Windows runner (`ubicloud-standard-2-windows`). Matches -# the Ubicloud Linux runner the evals workflow already uses, so billing -# stays consolidated under one provider. Swap to `windows-latest` to revert -# to GitHub's free runner if Ubicloud has Windows capacity issues. Note +# Runner: Ubicloud Windows runner (`ubicloud-standard-8-windows`). All CI +# in this repo runs on Ubicloud (Linux + Windows) for billing consolidation +# and consistent capacity. Swap to `windows-latest` to revert to GitHub's +# free runner if Ubicloud has Windows capacity issues. Note # `windows-latest-8-cores` (GitHub paid larger runner) sat queued because # org-level larger-runner billing wasn't enabled. # @@ -36,7 +36,7 @@ jobs: windows-free-tests: # Ubicloud Windows runner (same provider as the Linux evals workflow). # To revert: swap to `windows-latest` (GitHub's free 4-core Windows runner). - runs-on: ubicloud-standard-2-windows + runs-on: ubicloud-standard-8-windows timeout-minutes: 15 steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 24a347886..4c40d5e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Windows users who pull `git pull && ./setup` now get fresh skill files for every The browse server's Unicode sanitization lifts from `handleCommand` (PR #1463's original target) to `handleCommandInternal` so the batch command path (`/command/batch`) inherits it too. Both SSE producers (activity feed at `/activity/stream` and inspector stream) now stringify with a `sanitizeReplacer` function that cleans every string value during JSON.stringify — post-stringify regex is ineffective there because `JSON.stringify` has already converted `\uD800` into the escape sequence `"\\ud800"` before the regex would run. Result: every page-content payload that ships from the server has lone UTF-16 surrogate halves replaced with U+FFFD before any downstream consumer (Anthropic API, sidebar JSON.parse) sees them. -The `windows-free-tests.yml` CI lane migrates from GitHub-hosted `windows-latest` to `ubicloud-standard-2-windows` — same provider as the existing `ubicloud-standard-2` Linux evals runner, so billing stays consolidated. Earlier attempts used GitHub's paid `windows-latest-8-cores` larger runner but that queued indefinitely waiting for org-level larger-runner billing enablement. Four new wave tests get registered: sanitizer unit + bug-repro + wiring invariants, setup helper static-invariant + behavior matrix, build-script POSIX-shell sanity, and a doc-vs-config deprecated-key drift guard. Docs that still referenced the renamed `gbrain_sync_mode` config key now say `artifacts_sync_mode` consistently, and the drift guard prevents reintroduction. +All CI jobs migrate to Ubicloud runners (Linux + Windows on `ubicloud-standard-8` / `ubicloud-standard-8-windows`) for consolidated billing and consistent capacity. Eight workflows touch the runner pool: `evals.yml`, `evals-periodic.yml`, `ci-image.yml`, `windows-free-tests.yml`, `make-pdf-gate.yml`, `actionlint.yml`, `pr-title-sync.yml`, `skill-docs.yml`, `version-gate.yml`. Earlier ship attempts used GitHub's paid `windows-latest-8-cores` larger runner but it queued indefinitely waiting for org-level larger-runner billing enablement. `.github/actionlint.yaml` registers all four Ubicloud labels (`ubicloud-standard-2/8` and `ubicloud-standard-2/8-windows`) so future workflow edits don't have to fight the linter. Four new wave tests get registered: sanitizer unit + bug-repro + wiring invariants, setup helper static-invariant + behavior matrix, build-script POSIX-shell sanity, and a doc-vs-config deprecated-key drift guard. Docs that still referenced the renamed `gbrain_sync_mode` config key now say `artifacts_sync_mode` consistently, and the drift guard prevents reintroduction. Contributed by @realcarsonterry: PRs #1460, #1461, #1462, and #1463 are the seed of this wave. The scope expansion to all 42 setup sites + every server egress path + Windows CI migration is the gstack maintainer's follow-through. @@ -24,7 +24,7 @@ Source: this branch's diff against `origin/main` and the wave plan at `~/.claude | Bash brace groups in `package.json` build script (Bun-Windows-hostile) | 3 | 0 | -3 | | Stale `gbrain_sync_mode` references in docs | 5 | 0 | -5 | | New regression tests | 0 | 29 (4 files) | +29 | -| Windows CI runner | windows-latest (GitHub free) | ubicloud-standard-2-windows | consolidates Windows + Linux CI under one Ubicloud account | +| CI runner pool | mix of `ubuntu-latest` + `ubicloud-standard-2` + `windows-latest` | `ubicloud-standard-8` everywhere (Windows = `ubicloud-standard-8-windows`) | single billing surface, 4x more cores on Linux jobs, paid Windows that actually runs | The static invariant test (D7) reads `setup` and asserts zero raw `ln` calls outside the `_link_or_copy` helper body — even a single one-line slip by a future contributor fails the build. @@ -46,7 +46,8 @@ If you run gstack on Windows: `./setup` now produces a working install across ev - **`browse/src/server.ts`** — `handleCommandInternal` split into `handleCommandInternalImpl` (raw) + thin sanitizing wrapper. Single egress point for both HTTP and batch consumers. Inline INVARIANT comment near the wrapper documents the architectural constraint. - **`browse/src/server.ts` SSE producers** — activity feed (`/activity/stream`) and inspector stream stringify with `sanitizeReplacer`, a `JSON.stringify` replacer function that cleans every string value during encoding. Post-stringify regex is a no-op because `JSON.stringify` has already converted `\uD800` to `"\\ud800"` before the regex could match. Inline INVARIANT comment in each. - **`setup`** — new `_link_or_copy SRC DST` helper near `IS_WINDOWS` detection (~line 33). Auto-dispatches on file-vs-directory + Windows-vs-Unix, and skips Unix-style name-only aliases (e.g. `gstack/open-gstack-browser` for the connect-chrome alias) when the source doesn't resolve on disk so Windows installs don't abort under `set -e`. All 42 prior `ln -snf` call sites converted to `_link_or_copy`. New `_print_windows_copy_note_once` helper called from `link_claude_skill_dirs` after any link work completes. `cleanup_old_claude_symlinks` and `cleanup_prefixed_claude_symlinks` extended with a Windows branch so `--prefix` / `--no-prefix` flips remove stale real-file SKILL.md copies instead of leaving them behind. -- **`.github/workflows/windows-free-tests.yml`** — `runs-on: windows-latest` → `runs-on: ubicloud-standard-2-windows` (Ubicloud Windows runner, same provider as the Linux evals). `actionlint.yaml` registers the new label alongside `ubicloud-standard-2`. Test-list expanded to include the 4 new wave tests. +- **`.github/workflows/*.yml` (all 9 workflows)** — every `runs-on` switched to Ubicloud: `ubicloud-standard-8` for Linux (`evals.yml`, `evals-periodic.yml`, `ci-image.yml`, `actionlint.yml`, `pr-title-sync.yml`, `skill-docs.yml`, `version-gate.yml`, `make-pdf-gate.yml`'s Linux matrix entry) and `ubicloud-standard-8-windows` for Windows (`windows-free-tests.yml`). The `evals.yml` matrix default and the prose footer both updated to reference `ubicloud-standard-8`. Test-list in `windows-free-tests.yml` expanded to include the 4 new wave tests. +- **`.github/actionlint.yaml`** — registers all four Ubicloud labels (`ubicloud-standard-2/8` and `ubicloud-standard-2/8-windows`) so workflow lint stops rejecting any of them. The duplicate dead-weight `actionlint.yaml` at the repo root is removed (actionlint only reads `.github/actionlint.yaml`). - **`package.json`** — build script's three `{ git rev-parse HEAD 2>/dev/null || true; } > path/.version` brace groups replaced with `( ... )` subshells. POSIX-universal, Bun-Windows-compatible. - **`docs/gbrain-sync.md`, `docs/gbrain-sync-errors.md`** — 5 stale `gbrain_sync_mode` config-key references → `artifacts_sync_mode` (the rename landed in v1.27.0.0 but two docs still pointed at the old key). diff --git a/actionlint.yaml b/actionlint.yaml deleted file mode 100644 index c26c659f4..000000000 --- a/actionlint.yaml +++ /dev/null @@ -1,4 +0,0 @@ -self-hosted-runner: - labels: - - ubicloud-standard-2 - - ubicloud-standard-2-windows