Skip to content

perf(ci): all public-repo workflows → ubuntu-latest - #1626

Merged
HongmingWang-Rabbit merged 1 commit into
mainfrom
perf/public-workflows-ubuntu-latest
Apr 22, 2026
Merged

HongmingWang-Rabbit merged 1 commit into
mainfrom
perf/public-workflows-ubuntu-latest

Conversation

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor

Why

`molecule-core` is public — GHA-hosted minutes are free. The self-hosted Mac mini was originally in play to dodge GHA rate limits (memory `feedback_selfhosted_runner`), but for these 7 workflows it came with real costs:

  • Docker-push workflows emulated `linux/amd64` from arm64 via QEMU — every canvas + platform image build ran ~2-3x slower than native.
  • Six PRs worth of keychain-avoidance hacks in `publish-*` because `docker login` on macOS writes to osxkeychain unconditionally, and the Mac mini's launchd user-agent keychain is locked.
  • Homebrew pin-down env vars (`HOMEBREW_NO_*`) sprinkled everywhere to work around the shared `/opt/homebrew` symlink mess on the runner.
  • `setup-python@v5` couldn't write to `/Users/runner`, so `ci.yml python-lint` resorted to a hand-rolled Homebrew python3.11 dance.
  • Single runner → fan-out contention; CodeQL's 45-min analysis fought the canvas publish for the one slot.

What changes (per workflow)

Workflow Change
`ci.yml` (5 jobs) self-hosted → ubuntu-latest. shellcheck drops the existence check (preinstalled). python-lint swaps Homebrew python dance for `actions/setup-python@v5` + pip caching.
`canary-verify.yml` (2 jobs) self-hosted → ubuntu-latest. `brew install crane` + HOMEBREW_NO_* → `imjasonh/setup-crane@v0.4`.
`codeql.yml` self-hosted → ubuntu-latest. Drops `brew install jq` (preinstalled).
`e2e-api.yml` self-hosted → ubuntu-latest. Comment reworked — `services:` is no longer an unavailable option, though docker-run keeps the job's explicit readiness checks.
`promote-latest.yml` self-hosted → ubuntu-latest + setup-crane action.
`publish-canvas-image.yml` self-hosted → ubuntu-latest. Hand-rolled auths-map step + QEMU setup + buildx v4 → `docker/login-action@v3` + `setup-buildx@v3`. Native linux/amd64 build.
`publish-workspace-server-image.yml` Same pattern as canvas publish.

Net -96 lines across 7 files (mostly removed workarounds).

Blast radius

Large but mechanical: each workflow kept every functional step, only the runner, auth, and tool-installation bits changed. The self-hosted Mac mini stays online for private-repo workflows (`molecule-controlplane`, `molecule-tenant-proxy`, etc.) — no change there.

Cost math

  • Before: canvas publish + platform publish + CodeQL × 3 langs + CI jobs + E2E + canary… all funnelled through one arm64 Mac mini, serialising under the job queue.
  • After: parallel ubuntu-latest runners, free on public repos. Upper bound for heavy usage (2000 min/mo) at the $0.008/min private-Linux rate = $16 — and this is a public repo so actual cost is $0.

Test plan

  • YAML parses
  • Merge → first push runs CI through on ubuntu-latest (changes, platform-build, canvas-build, shellcheck, canvas-deploy-reminder, python-lint)
  • Next main merge runs publish-canvas-image + publish-workspace-server-image cleanly with docker/login-action
  • Next CodeQL schedule / push cleans through
  • e2e-api passes on ubuntu-latest (Postgres + Redis start via docker-run as before)
  • `promote-latest` workflow_dispatch works with setup-crane (we'll test this the next time a rollback is needed, or run a dispatch dry-run now)

🤖 Generated with Claude Code

molecule-core is a public repo — GHA-hosted minutes are free. The
self-hosted Mac mini was only in play to dodge GHA rate limits
(memory feedback_selfhosted_runner), but for these specific
workflows it came with real costs:

- Docker-push workflows emulated linux/amd64 from arm64 via QEMU —
  every canvas + platform image build ran ~2-3x slower than native.
- Six PRs worth of keychain-avoidance hacks in publish-* because
  `docker login` on macOS writes to osxkeychain unconditionally,
  and the Mac mini's launchd user-agent keychain is locked.
- Homebrew pin-down environment variables (HOMEBREW_NO_*) sprinkled
  everywhere to work around the shared /opt/homebrew symlink mess
  on the runner.
- Setup-python@v5 couldn't write to /Users/runner, so ci.yml
  python-lint resorted to a hand-rolled Homebrew python3.11 dance.
- Single runner → fan-out contention; CodeQL's 45-min analysis
  fought the canvas publish for the one slot.

Changes across the 7 workflows:

- runs-on: [self-hosted, macos, arm64] → ubuntu-latest (every job)
- publish-canvas-image + publish-workspace-server-image:
  drop the hand-rolled auths-map step + QEMU setup + buildx v4
  → docker/login-action@v3 + setup-buildx@v3. Linux + amd64
  target = native build.
- canary-verify + promote-latest: replace `brew install crane` +
  HOMEBREW_NO_* incantations with imjasonh/setup-crane@v0.4.
- codeql.yml: drop `brew install jq` — jq is preinstalled on
  ubuntu-latest.
- ci.yml shellcheck: drop the self-hosted existence check —
  shellcheck is preinstalled via apt.
- ci.yml python-lint: replace the Homebrew python3.11 path dance
  with actions/setup-python@v5 (which works fine on GHA-hosted),
  add requirements.txt caching while we're there.
- Remove stale comments referencing "the self-hosted runner",
  "Mac mini", keychain, osxkeychain etc.

The self-hosted Mac mini remains in service for private-repo
workflows only. Memory feedback_selfhosted_runner updated to
reflect the public-repo scope carve-out.

Net -96 lines across the 7 files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@HongmingWang-Rabbit
HongmingWang-Rabbit merged commit 557e7a0 into main Apr 22, 2026
8 of 10 checks passed
molecule-ai Bot pushed a commit that referenced this pull request Apr 23, 2026
…k seed

Two latent bugs the self-hosted Mac mini had been hiding. Both caught
by the newer toolchain on ubuntu-latest runners after PR #1626.

1. workspace-server/internal/handlers/terminal.go:442
   `fmt.Sprintf("%s:%d", host, port)` flagged by go vet as unsafe
   for IPv6 (it omits the required [::] brackets). Replaced with
   `net.JoinHostPort(host, strconv.Itoa(port))` which handles both
   IPv4 and IPv6 correctly. No runtime behaviour change — the only
   call site passes "127.0.0.1", so the bug would never trigger in
   practice, but vet is right to flag it as a latent correctness
   issue.

2. workspace/tests/test_a2a_executor.py::test_set_current_task_updates_heartbeat
   `MagicMock()` auto-creates attributes on first access, so
   `getattr(heartbeat, "active_tasks", 0)` in shared_runtime.py
   returned a MagicMock rather than the default 0. Adding 1 to a
   MagicMock returns another MagicMock, so the assertion
   `heartbeat.active_tasks == 1` never held. Seeding
   `heartbeat.active_tasks = 0` before the first call makes
   getattr() return a real int, matching how the real HeartbeatLoop
   class initialises itself.

Both pre-existed on main and were hidden by the older Python / Go
toolchains on the Mac mini runner. Verified locally (venv pytest
pass, `go vet ./...` + `go build ./...` clean on workspace-server).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 23, 2026
…k seed

Two latent bugs the self-hosted Mac mini had been hiding. Both caught
by the newer toolchain on ubuntu-latest runners after PR #1626.

1. workspace-server/internal/handlers/terminal.go:442
   `fmt.Sprintf("%s:%d", host, port)` flagged by go vet as unsafe
   for IPv6 (it omits the required [::] brackets). Replaced with
   `net.JoinHostPort(host, strconv.Itoa(port))` which handles both
   IPv4 and IPv6 correctly. No runtime behaviour change — the only
   call site passes "127.0.0.1", so the bug would never trigger in
   practice, but vet is right to flag it as a latent correctness
   issue.

2. workspace/tests/test_a2a_executor.py::test_set_current_task_updates_heartbeat
   `MagicMock()` auto-creates attributes on first access, so
   `getattr(heartbeat, "active_tasks", 0)` in shared_runtime.py
   returned a MagicMock rather than the default 0. Adding 1 to a
   MagicMock returns another MagicMock, so the assertion
   `heartbeat.active_tasks == 1` never held. Seeding
   `heartbeat.active_tasks = 0` before the first call makes
   getattr() return a real int, matching how the real HeartbeatLoop
   class initialises itself.

Both pre-existed on main and were hidden by the older Python / Go
toolchains on the Mac mini runner. Verified locally (venv pytest
pass, `go vet ./...` + `go build ./...` clean on workspace-server).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 23, 2026
…k seed

Two latent bugs the self-hosted Mac mini had been hiding. Both caught
by the newer toolchain on ubuntu-latest runners after PR #1626.

1. workspace-server/internal/handlers/terminal.go:442
   `fmt.Sprintf("%s:%d", host, port)` flagged by go vet as unsafe
   for IPv6 (it omits the required [::] brackets). Replaced with
   `net.JoinHostPort(host, strconv.Itoa(port))` which handles both
   IPv4 and IPv6 correctly. No runtime behaviour change — the only
   call site passes "127.0.0.1", so the bug would never trigger in
   practice, but vet is right to flag it as a latent correctness
   issue.

2. workspace/tests/test_a2a_executor.py::test_set_current_task_updates_heartbeat
   `MagicMock()` auto-creates attributes on first access, so
   `getattr(heartbeat, "active_tasks", 0)` in shared_runtime.py
   returned a MagicMock rather than the default 0. Adding 1 to a
   MagicMock returns another MagicMock, so the assertion
   `heartbeat.active_tasks == 1` never held. Seeding
   `heartbeat.active_tasks = 0` before the first call makes
   getattr() return a real int, matching how the real HeartbeatLoop
   class initialises itself.

Both pre-existed on main and were hidden by the older Python / Go
toolchains on the Mac mini runner. Verified locally (venv pytest
pass, `go vet ./...` + `go build ./...` clean on workspace-server).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@HongmingWang-Rabbit
HongmingWang-Rabbit deleted the perf/public-workflows-ubuntu-latest branch April 24, 2026 00:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant