Skip to content

perf(ci): pin the light validation jobs to GitHub-hosted runners - #2237

Closed
willgriffin wants to merge 1 commit into
mainfrom
perf/issue-2236-hosted-light-jobs
Closed

perf(ci): pin the light validation jobs to GitHub-hosted runners#2237
willgriffin wants to merge 1 commit into
mainfrom
perf/issue-2236-hosted-light-jobs

Conversation

@willgriffin

Copy link
Copy Markdown
Contributor

Phase 0 of happyvertical/iac#1349 for this repository: reserve the metal fleet
for the jobs that actually benefit from it.

What moves

Three test-suite.yml jobs get a plain runs-on: ubuntu-latest:

job why it gains nothing from metal hosted, measured
affected-scope (Detect Affected Validation Scope) checkout + dorny/paths-filter; no Turbo task, no install 6 s
lint (Lint) Biome via npx; deliberately never runs the workspace install 18 s
test-packages-result (Test Packages) no checkout and no setup at all — reads needs.*.result and exits seconds (required-ci is the same shape, 3 s)

Measurements are from the full hosted rehearsal
(run 30969467329),
cold and cacheless — that run was on pull_request_target, where the Turbo
shim is refused, so nothing restored.

None of the three invokes setup-environment, so none can restore from the
internal Turbo cache or the hosted shim, and the fleet's memory is irrelevant
to all of them. On the fleet they could instead wait out a netboot of up to
780 s or queue behind the heavy shards on an 8-slot pool. smrt is public, so
hosted minutes are free and unmetered: the move costs nothing and returns
three slots per validation pass.

Mechanics

  • Pinned jobs take a plain label, not a lever. An expression that can only
    ever resolve to ubuntu-latest reads as a choice when there is none, and
    these jobs need no fleet-outage escape hatch because they never depend on the
    fleet.
  • The CI_HOSTED_FALLBACK_ENABLED lever stays intact on all ten jobs that
    remain on metal
    . Its shared explanation moved from affected-scope to
    build, the first job in the file that still carries it.
  • Job names are byte-identical to main, so the sole required context
    (Required CI) and the two lifecycle rulesets are unaffected.
  • .github/actionlint.yaml needs no change — actionlint does not validate
    labels inside expressions.
  • Timeouts come down with the jobs, per CI.md's existing rule that hosted
    ceilings may track observed runtime because hosted runners never enter the
    self-hosted queue: 10 min for the two that check out, 5 for the aggregator
    (matching required-ci).

This also resolves an asymmetry CI.md recorded as unfinished business:
test-packages-result sat on metal while required-ci — structurally the
same job — was hosted. Backing a required status argues for starting promptly,
not for queueing.

ONNX: a non-issue for this set

setup-environment installs ONNX system deps via sudo apt-get when
CI_ONNX_DEPS_READY is unset (the hosted case), costing 1-2 min per job. That
is a real lever, but none of these three jobs invokes that composite action
at all
, so there is no install-deps: 'false' to apply here. Recorded so the
next hosted move of a job that does use it starts from the right place.

Deliberately not moved

When this first executes

on-pull-request.yml uses pull_request_target, so this PR's own checks run
against main's YAML
— with these jobs still on metal. The pinned selection
first executes in the merge queue. That is expected, not a gap; it is also why
a mistake here would eject from the queue rather than redden the PR.

Validation

  • actionlint .github/workflows/test-suite.yml — clean
  • yamllint .github/workflows/test-suite.yml — clean
  • node scripts/publish-workflow-policy.test.mjs — 1 pass, 0 fail
  • pnpm smrt dev:knowledge-check✓ SMRT knowledge is fresh, 0 errors, 0
    warnings
  • Runner split verified mechanically: 3 plain ubuntu-latest, 10 lever
    expressions remaining, job names diffed identical against origin/main
  • Independent codex review could not run — the account is out of credits
    until Aug 7. Covered by own review, the GitHub auto-reviewers, and CI.

Follow-up

.github/CI.md carries an empty before/after table under Phase 0 hosted
pinning
, to be filled after merge with the moved jobs' durations and — the
result that actually matters — whether the heavy shards' queue wait improved
from the freed slots. If it did not, the freed slots were not the binding
constraint, and the capacity oracle in phases 1-2 of the epic is the next
lever rather than a wider pinning.

Closes #2236
Refs happyvertical/iac#1349

{"schema":"hv-agent-run:v1","runtime":"claude","session":"524cd2c5-11c6-450e-b137-756d97181723","issue":"2236","policy_revision":"1.0.0","validation":["actionlint clean on .github/workflows/test-suite.yml","yamllint clean on .github/workflows/test-suite.yml","node scripts/publish-workflow-policy.test.mjs: 1 pass 0 fail","pnpm smrt dev:knowledge-check: SMRT knowledge is fresh, 0 errors 0 warnings","runner split verified: 3 plain ubuntu-latest, 10 CI_HOSTED_FALLBACK_ENABLED lever expressions remaining","job names diffed byte-identical against origin/main, so the required context Required CI is unaffected","repo confirmed public, so the free-hosted-minutes premise holds","hosted durations sourced from measured run 30969467329, not estimated","codex review unavailable (account out of credits until Aug 7); covered by own review, GitHub auto-reviewers, and CI"]}

`affected-scope`, `lint`, and `test-packages-result` gain nothing from the
metal fleet. None runs a Turbo task or `setup-environment`, so none can
restore from the internal cache or the hosted shim, and the fleet's memory is
irrelevant to all three. On the fleet they could instead wait out a netboot of
up to 780s, or queue behind the heavy shards on an 8-slot pool.

Hosted minutes are free and unmetered on this public repository, so pinning
them to a plain `ubuntu-latest` costs nothing and returns three slots per
validation pass to the work that actually needs them. Measured hosted on the
full rehearsal (run 30969467329): 6s for the scope filter and 18s for lint.
The aggregator does no checkout at all and is structurally identical to the
already-hosted `required-ci`.

The pinned jobs take a plain label rather than the CI_HOSTED_FALLBACK_ENABLED
selector: a lever that can only ever resolve to ubuntu-latest reads as a
choice when there is none, and these jobs need no fleet-outage escape hatch
because they never depend on the fleet. The lever stays intact on every job
that remains on metal, and its shared explanation moves to `build` — the first
job in the file that still carries it.

Timeouts come down with the jobs, per the existing rule that hosted ceilings
may track observed runtime because hosted runners never enter the self-hosted
queue: 10 minutes for the two that check out, 5 for the aggregator, matching
`required-ci`.

This also resolves the aggregation asymmetry CI.md recorded, where
`test-packages-result` sat on metal while `required-ci` — the same shape of
job — was hosted. Backing a required status argues for starting promptly, not
for queueing.

Deliberately not moved: test-core x3, test-packages x3, Coverage Gate, Build,
and Typecheck, whose hosted duration under merge_group full mode has never
been measured and must not move on assumption; and postgres-tests.yml, whose
services container needs the dind lane.

Refs #2236
Refs happyvertical/iac#1349
Copilot AI review requested due to automatic review settings August 5, 2026 19:29
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements phase 0 of runner pinning for SMRT’s CI by moving “light” test-suite.yml jobs that do not benefit from the self-hosted fleet (no Turbo tasks, no setup-environment, no cache usage) onto GitHub-hosted runners, freeing self-hosted capacity for heavier shards while keeping the emergency hosted-fallback lever for the remaining fleet-bound jobs.

Changes:

  • Pin affected-scope, lint, and test-packages-result to runs-on: ubuntu-latest and reduce their timeouts to hosted-appropriate ceilings.
  • Relocate the shared CI_HOSTED_FALLBACK_ENABLED explanation block to the first job that still uses the lever (build) and explicitly document which jobs are intentionally outside it.
  • Update .github/CI.md to reflect the new steady-state runner split, timeout rationale, and phase-0 measurement table for post-merge tracking.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
.github/workflows/test-suite.yml Pins three lightweight jobs to hosted runners, adjusts timeouts, and moves the fallback-lever documentation to build.
.github/CI.md Updates CI architecture docs to reflect the runner split, timeout guidance, and adds a Phase 0 measurement section.

@willgriffin

Copy link
Copy Markdown
Contributor Author

This PR's own validation measured the problem it fixes

Because on-pull-request.yml runs on pull_request_target, these checks
executed against main's YAML — so the two jobs this PR pins to hosted ran
on the metal fleet one last time. Their timings from
run 31039651772:

job queue wait execution hosted (rehearsal 30969467329)
Detect Affected Validation Scope 30 m 11 s 13 s 6 s
Lint 30 m 16 s 49 s 18 s

Both were created at 19:29:33Z and did not start until 19:59:44Z and
19:59:49Z. That is 60 m 27 s of fleet queue wait to perform 62 s of work
a 139:1 and 37:1 wait-to-work ratio, for two jobs that cannot use the internal
Turbo cache and do not need the fleet's memory. They also held two of the
pool's eight slots while Coverage Gate and Prepare Publish Validation
waited behind them.

This is a single observation, not a distribution, and it landed on a busy
pool — an idle fleet would have started them immediately. It is not offered as
the acceptance measurement; .github/CI.md still asks for ten runs either side
after merge, and the second-order effect on the heavy shards' queue wait
remains the result that matters. But it is a concrete instance of the failure
mode the change targets, measured on the change's own PR.

Validate Workflow Files passed against the PR head, so the new YAML is
actionlint- and yamllint-clean as committed. The pinned runner selection itself
first executes in the merge queue, which loads workflow YAML from the merge
ref rather than from main.

@willgriffin

Copy link
Copy Markdown
Contributor Author

Superseded by #2242, which carries this branch's commits cherry-picked verbatim (same authors, same messages) alongside the other two CI-lane fixes.

Rationale: these three PRs each needed a full validation pass on the very lane they were unclogging, and #2217/#2237 both edit .github/workflows/test-suite.yml. Landing them as one PR at the front of the queue means one validation cycle and one merge, after which the rest of the board can drain.

The merged result is the union of both workflow changes, verified job by job — the three light jobs are hosted per #2236, every heavy job keeps the CI_HOSTED_FALLBACK_ENABLED selector, and coverage-gate keeps #2217's narrowed if: inputs.mode == 'full' (this branch predated that change where applicable). actionlint is clean on all three workflows.

The branch is preserved. Reopen if #2242 needs to be unwound.

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.

perf(ci): pin the light validation jobs to GitHub-hosted runners

2 participants