ci(windows): revert to free windows-latest

Ubicloud doesn't ship Windows runners — confirmed via their docs. The
`ubicloud-standard-*-windows` labels I added do not exist and were causing
`windows-free-tests` to sit "Queued — Waiting to run this check" forever
(GitHub Actions can't tell a typoed label from a self-hosted runner that's
about to register; it just waits).

Three prior Windows-runner attempts all failed for different reasons:
- `blacksmith-2vcpu-windows-2022` — Blacksmith app not installed on the org
- `windows-latest-8-cores` — GitHub paid larger-runner billing not enabled
- `ubicloud-standard-2/8-windows` — Ubicloud doesn't offer Windows at all

The free `windows-latest` runner (4 cores, ~60s spin-up, $0) is the one
path that actually runs. The wave-coverage Windows tests are <30s of real
work; total job time stays under 2 minutes.

Cleaned up `.github/actionlint.yaml` to drop the bogus
`ubicloud-standard-*-windows` entries — kept only the two real Linux labels.

CHANGELOG: split the runner-pool row into Linux (migrated to Ubicloud-8)
vs Windows (stays on free windows-latest), with the why on each. Itemized
line for windows-free-tests rewritten to reflect the actual outcome.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-05-14 21:02:35 -07:00
parent 818bb02f28
commit ae6b25f825
3 changed files with 12 additions and 13 deletions
-2
View File
@@ -2,5 +2,3 @@ self-hosted-runner:
labels:
- ubicloud-standard-2
- ubicloud-standard-8
- ubicloud-standard-2-windows
- ubicloud-standard-8-windows
+6 -7
View File
@@ -8,12 +8,11 @@ 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-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.
# Runner: GitHub-hosted free `windows-latest`. The whole rest of CI runs on
# Ubicloud (Linux), but Ubicloud doesn't ship Windows runners and we don't
# want to flip on GitHub's org-level larger-runner billing for just this one
# job. 4 cores, ~60s spin-up, $0. The wave-coverage tests this runs are
# small enough that total job time stays under 2 minutes.
#
# What this DOES NOT do (still out of scope, tracked as follow-up):
# - Run the full free suite on Windows. The 24 tests that hardcode /bin/sh,
@@ -36,7 +35,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-8-windows
runs-on: windows-latest
timeout-minutes: 15
steps:
+6 -4
View File
@@ -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.
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.
All Linux CI jobs migrate to `ubicloud-standard-8` for consolidated billing and 4x more cores than free `ubuntu-latest`. Eight workflows touch the Linux pool: `evals.yml`, `evals-periodic.yml`, `ci-image.yml`, `make-pdf-gate.yml`, `actionlint.yml`, `pr-title-sync.yml`, `skill-docs.yml`, `version-gate.yml`. The Windows-only job (`windows-free-tests.yml`) stays on GitHub's free `windows-latest` — Ubicloud doesn't ship a Windows pool, GitHub's paid `windows-latest-8-cores` requires org-level larger-runner billing enablement, and the wave-coverage tests this job runs are small enough that the slower 4-core free runner keeps total job time under 2 minutes. 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,8 @@ 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 |
| 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 |
| Linux CI runner pool | mix of `ubuntu-latest` (4 core, free) + `ubicloud-standard-2` | `ubicloud-standard-8` everywhere | single billing surface for Linux, 4x more cores on previously-free jobs |
| Windows CI runner | `windows-latest` (free) | `windows-latest` (free, unchanged) | Ubicloud doesn't offer Windows; paid GitHub larger-runner option requires org-billing toggle not currently set |
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,8 +47,9 @@ 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/*.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`).
- **`.github/workflows/*.yml` (8 Linux workflows)** — every Linux `runs-on` switched to `ubicloud-standard-8`: `evals.yml`, `evals-periodic.yml`, `ci-image.yml`, `actionlint.yml`, `pr-title-sync.yml`, `skill-docs.yml`, `version-gate.yml`, and `make-pdf-gate.yml`'s Linux matrix entry. The `evals.yml` matrix default and the prose footer both updated to reference `ubicloud-standard-8`.
- **`.github/workflows/windows-free-tests.yml`** — stays on GitHub-hosted free `windows-latest`. Test-list expanded to include the 4 new wave tests. Earlier attempts on Blacksmith/GitHub-larger/Ubicloud-Windows all failed (label not registered, org-billing off, vendor doesn't offer Windows respectively); free `windows-latest` is the working path.
- **`.github/actionlint.yaml`** — registers the two Ubicloud Linux labels (`ubicloud-standard-2`, `ubicloud-standard-8`) so workflow lint accepts 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).