Skip to content

ci: consolidate CI workflow from 15 jobs to 8 - #1996

Merged
thymikee merged 6 commits into
mainfrom
ci/optimize-checks-2
Aug 24, 2026
Merged

ci: consolidate CI workflow from 15 jobs to 8#1996
thymikee merged 6 commits into
mainfrom
ci/optimize-checks-2

Conversation

@thymikee

@thymikee thymikee commented Aug 24, 2026

Copy link
Copy Markdown
Member

Summary

A typical code PR used to start ~19 ubuntu workers, most running a 25-second gate after a full checkout and install; queueing for workers dominated wall time. This consolidates and re-shards the CI surface with no gate removed or weakened — every check remains a named run-gate step, and the gate manifest derives lane ownership structurally from the workflow YAML.

Job layout changes:

Change Detail
CI workflow 15 → 8 jobs Lint & Format (+ the static text assertions, run pre-install), Repo Guards (layering/selector/wiring/maestro/mcp-metadata/freerange), Compatibility & Provenance (fallow + replay-compat + released-surface-compat sharing one deep checkout), Typecheck & Package, Coverage shards ×2 + Coverage Report merge job, Integration Tests (absorbs the web platform smoke with step-scoped env)
macOS lanes merged The host-XCTest lane folds into the macOS smoke job: one runner builds one unit-test-flagged bundle consumed by both the host XCTest run (pnpm check:xctest-selection follows it to macos.yml) and the replay smoke. Removes the second macos-26 queue wait, which measured 5–6 min per PR.
Coverage sharded Two shards write blob reports (~3m25 each, down from 5m46); Coverage Report merges them, evaluates thresholds once over the full suite, and produces every coverage artifact. The tmpdir leak check runs per shard, since a leak lands on whichever runner executed the file. Shard/merge switches live in vitest.config.ts behind AGENT_DEVICE_COVERAGE_SHARD/AGENT_DEVICE_COVERAGE_MERGE.
FreeRange de-Bunned @chenglou/freerange's bin is a plain Node script; the Bun setup is gone and the gate joins Repo Guards as its last step. Still GitHub-owned (localRunnable stays false) so the pre-push affected path does not pay its ~3 min.
Fixture release no-ops skipped The Test App Build Cache matrix filters to entries that will actually build, so cached-fingerprint PRs start zero release runners instead of two 5-second jobs.

Typical PR check lines drop from 28 to ~18–20 (the four CodeQL lines are repo-settings default setup, intentionally untouched). Ubuntu worker demand drops by roughly half; every non-device ubuntu lane now completes in under 5 minutes.

Validation

Evidence at 356ce79; all 20 checks green on that head:

  • The merged macOS lane ran end to end on the PR head: host XCTest suite executed with the source-derived executed-count assertion passing, and the macOS replay passed against the unit-test-flagged binary — the live evidence for the one behavior-affecting merge.
  • Coverage pipeline: both shards green at ~3m25, Coverage Report merged blobs and passed the changed-line gate in 34s.
  • pnpm check:gate-manifest → ok (48 checks wired); gate manifest tests 49/49, affected-selector tests 62/62, xctest-selection tests 37/37; pnpm check:affected --run, typecheck, lint, format clean.
  • Local smoke of the shard→blob→merge cycle verified blob union and threshold evaluation before pushing.

Two issues surfaced by real runs and fixed here: vitest enforces coverage thresholds per shard (shards report ungated; only the merged run gates), and upload-artifact v4 silently skips dot-prefixed directories without include-hidden-files.

Known pre-existing flake exposed once under shard load (not from this change): src/daemon/__tests__/request-save-script-transports.test.ts hit ENOTEMPTY in its own afterEach cleanup racing an async daemon-log write. It passed on re-run and repeatedly in isolation; worth a follow-up issue.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.40 MB 2.40 MB 0 B
JS gzip 805.1 kB 805.1 kB 0 B
npm tarball 929.5 kB 929.5 kB 0 B
npm unpacked 3.22 MB 3.22 MB 0 B

npm unpacked components

Component Base Current Diff
JS / dist source 2.55 MB 2.55 MB 0 B
Apple runner source/project 570.6 kB 570.6 kB 0 B
macOS helper source 54.5 kB 54.5 kB 0 B
Android helper artifacts 0 B 0 B 0 B
Other package files 44.5 kB 44.5 kB 0 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.0 ms 28.1 ms +1.1 ms
CLI --help 74.6 ms 76.6 ms +2.0 ms

Top changed chunks: no changes in the largest emitted chunks.

Top changed packed files

No changed packed files.

@thymikee

Copy link
Copy Markdown
Member Author

This was generated by AI during triage.

Cross-PR finding with #1994 (currently open, all-green): once both land, the coverage lane breaks.

#1994 changes test:coverage:ci to two chained invocations (vitest run --coverage --project=!fuzz-worker && vitest run --project fuzz-worker). AGENT_DEVICE_COVERAGE_SHARD is job-level env here, so it applies to both legs: on each shard runner the uninstrumented fuzz-worker leg overwrites .vitest-reports/blob-<shard>.json written by the instrumented leg (same shard id → same blob filename), and the uploaded artifact loses the instrumented results that Coverage Report merges and gates on. Symmetrically, this PR's Coverage Report job runs gate: unit-ci with AGENT_DEVICE_COVERAGE_MERGE=1, which would execute #1994's second leg in merge mode — re-merging blobs instead of running fuzz-worker.

Neither PR is wrong alone; they need one reconciliation before both merge. Detail posted on #1994.

Separately, good news: the "known pre-existing flake" this PR documents (request-save-script-transports.test.ts ENOTEMPTY) already has a fix up in #1999 (flushes flushSessionEventLogWrites() before the temp-root cleanup), so the follow-up issue can close when these land.

thymikee added a commit that referenced this pull request Aug 24, 2026
The Coverage job intermittently ends with no failing test and one file's
results missing:

    Test Files  1070 passed (1071)
    Errors      1 error
    Error: [vitest-pool]: Worker forks emitted error.
    Caused by: Error: Worker exited unexpectedly

This is shape (B) of #1824 — the half #1854 did not fix. Scanning every
failed Coverage job across the 120 CI runs since #1854 merged finds the
signature five times, and the vanished file is
scripts/fuzz/corpus-replay.test.ts all five (six for six with #1866's
occurrence): 23% of Coverage failures in that window, ~4% of all CI runs.

The ~40s gap before the error is coverage report generation, not test
time — the pool surfaces its AggregateError only once every task settles.
Control, from a green attempt of the same run: the file passes in 3152ms
at 09:37:35.9 and the summary prints at 09:38:12.5. So the file is not
slow in CI, nothing else is in flight when it dies (subprocess-stub is
serialized and unit-core has drained), and neither a missed per-case
budget nor STARTUP_BUDGET_MS is implicated. Partial test counts (3/11 and
9/11 reported) place the death mid-file, inside runCases.

So the corpus replay gets its own serialized project that the coverage
run skips, and a second uninstrumented Vitest invocation in
`test:coverage:ci` runs it, keeping the tests on every PR. Measured
against two full runs, this costs zero coverage: the cases execute in
worker threads, a separate isolate the fork's inspector never
instruments, so the lines reported are identical with and without it.

Membership is by demonstrated failure, not by a property of the code:
`session-replay-runtime-maestro.test.ts` also constructs a
node:worker_threads Worker and stays in unit-core, instrumented and
green, so "nests a Worker" is explicitly not the criterion.

The second leg runs through `test:fuzz-worker`, which blanks
AGENT_DEVICE_COVERAGE_SHARD and AGENT_DEVICE_COVERAGE_MERGE. #1996 sets
those as job-level env over a single `gate: unit-ci` step, so both legs
would otherwise inherit them. Verified on a local merge of the two
branches: without the blanking the shard fails outright, because Vitest
refuses `--shard=1/2` over this one-file project and the blob reporter
overwrites the instrumented shard's report on the way out. With it, both
shards and the merge job pass and the merged report carries all 1094
files.

Refs #1824
thymikee added a commit that referenced this pull request Aug 24, 2026
The Coverage job intermittently ends with no failing test and one file's
results missing:

    Test Files  1070 passed (1071)
    Errors      1 error
    Error: [vitest-pool]: Worker forks emitted error.
    Caused by: Error: Worker exited unexpectedly

This is shape (B) of #1824 — the half #1854 did not fix. Scanning every
failed Coverage job across the 120 CI runs since #1854 merged finds the
signature five times, and the vanished file is
scripts/fuzz/corpus-replay.test.ts all five (six for six with #1866's
occurrence): 23% of Coverage failures in that window, ~4% of all CI runs.

The ~40s gap before the error is coverage report generation, not test
time — the pool surfaces its AggregateError only once every task settles.
Control, from a green attempt of the same run: the file passes in 3152ms
at 09:37:35.9 and the summary prints at 09:38:12.5. So the file is not
slow in CI, nothing else is in flight when it dies (subprocess-stub is
serialized and unit-core has drained), and neither a missed per-case
budget nor STARTUP_BUDGET_MS is implicated. Partial test counts (3/11 and
9/11 reported) place the death mid-file, inside runCases.

So the corpus replay gets its own serialized project that the coverage
run skips, and a second uninstrumented Vitest invocation in
`test:coverage:ci` runs it, keeping the tests on every PR. Measured
against two full runs, this costs zero coverage: the cases execute in
worker threads, a separate isolate the fork's inspector never
instruments, so the lines reported are identical with and without it.

Membership is by demonstrated failure, not by a property of the code:
`session-replay-runtime-maestro.test.ts` also constructs a
node:worker_threads Worker and stays in unit-core, instrumented and
green, so "nests a Worker" is explicitly not the criterion.

The second leg runs through `test:fuzz-worker`, which blanks
AGENT_DEVICE_COVERAGE_SHARD and AGENT_DEVICE_COVERAGE_MERGE. #1996 sets
those as job-level env over a single `gate: unit-ci` step, so both legs
would otherwise inherit them. Verified on a local merge of the two
branches: without the blanking the shard fails outright, because Vitest
refuses `--shard=1/2` over this one-file project and the blob reporter
overwrites the instrumented shard's report on the way out. With it, both
shards and the merge job pass and the merged report carries all 1094
files.

Refs #1824
@thymikee

Copy link
Copy Markdown
Member Author

Review of exact head 356ce79b5ff131d2c0227b5d8d65ea4912a4f178: no code-level findings. Gate ownership remains intact, both coverage shards merge to the full union with thresholds enforced at merge, FreeRange runs under Node, fixture release matrices skip correctly, and the combined macOS lane proves exactly 179 source-derived host XCTests before a passing replay.

The head is not ready for the label only because its base is stale: current main is 66458c7916e59e56ab469a06f5e1f30171240fff, and main added Apple runner XCTest sources that this PRs host-XCTest migration directly owns. Rebase onto current main and rerun exact-head CI; no implementation change is otherwise indicated.

Merge order: #1996 first, then rebase and revalidate #1994, whose environment clearing is a follow-on adjustment.

thymikee added a commit that referenced this pull request Aug 24, 2026
The Coverage job intermittently ends with no failing test and one file's
results missing:

    Test Files  1070 passed (1071)
    Errors      1 error
    Error: [vitest-pool]: Worker forks emitted error.
    Caused by: Error: Worker exited unexpectedly

This is shape (B) of #1824 — the half #1854 did not fix. Scanning every
failed Coverage job across the 120 CI runs since #1854 merged finds the
signature five times, and the vanished file is
scripts/fuzz/corpus-replay.test.ts all five (six for six with #1866's
occurrence): 23% of Coverage failures in that window, ~4% of all CI runs.

The ~40s gap before the error is coverage report generation, not test
time — the pool surfaces its AggregateError only once every task settles.
Control, from a green attempt of the same run: the file passes in 3152ms
at 09:37:35.9 and the summary prints at 09:38:12.5. So the file is not
slow in CI, nothing else is in flight when it dies (subprocess-stub is
serialized and unit-core has drained), and neither a missed per-case
budget nor STARTUP_BUDGET_MS is implicated. Partial test counts (3/11 and
9/11 reported) place the death mid-file, inside runCases.

So the corpus replay gets its own serialized project that the coverage
run skips, and a second uninstrumented Vitest invocation in
`test:coverage:ci` runs it, keeping the tests on every PR. Measured
against two full runs, this costs zero coverage: the cases execute in
worker threads, a separate isolate the fork's inspector never
instruments, so the lines reported are identical with and without it.

Membership is by demonstrated failure, not by a property of the code:
`session-replay-runtime-maestro.test.ts` also constructs a
node:worker_threads Worker and stays in unit-core, instrumented and
green, so "nests a Worker" is explicitly not the criterion.

The second leg runs through `test:fuzz-worker`, which blanks
AGENT_DEVICE_COVERAGE_SHARD and AGENT_DEVICE_COVERAGE_MERGE. #1996 sets
those as job-level env over a single `gate: unit-ci` step, so both legs
would otherwise inherit them. Verified on a local merge of the two
branches: without the blanking the shard fails outright, because Vitest
refuses `--shard=1/2` over this one-file project and the blob reporter
overwrites the instrumented shard's report on the way out. With it, both
shards and the merge job pass and the merged report carries all 1094
files.

Refs #1824
Merge single-gate ubuntu jobs into grouped jobs sharing one checkout and
install: Lint & Format (plus the static text assertions), Repo Guards
(layering/selector/wiring/maestro/mcp-metadata), Compatibility &
Provenance (shared fetch-depth: 0 checkout), Typecheck & Package, and
Integration Tests (absorbs the web smoke with step-scoped env). Every
gate remains an independently named run-gate step; the gate manifest
derives lane ownership structurally.

Drop the Bun setup from FreeRange: @chenglou/freerange's bin is a plain
Node script. It stays GitHub-owned; only the runtime requirement is
retired.
FreeRange runs on plain Node now, so its gate joins Repo Guards as the
last step instead of occupying its own worker for the slowest guard.
The fixture release matrix filters to entries that will actually build,
so a cached-fingerprint PR starts zero release runners.
The macOS lane now builds one unit-test-flagged runner bundle that both
the host XCTest run and the replay smoke consume, so the host lane no
longer occupies its own macos-26 runner behind a separate queue. The
host lane's file moves with it, and check:xctest-selection follows.

Coverage shards across two runners via blob reports and merges them on
a report job that evaluates thresholds once over the full suite and
produces every coverage artifact. The tmpdir leak check runs per shard,
since a leak lands on whichever runner executed the file.
A shard evaluates its own half-suite coverage, so the global gate fired
per shard. Shards now report without gating; Coverage Report keeps the
real thresholds over the full merged suite.
@thymikee
thymikee force-pushed the ci/optimize-checks-2 branch from 356ce79 to bc48201 Compare August 24, 2026 14:27
@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Aug 24, 2026
@thymikee

Copy link
Copy Markdown
Member Author

Re-review of exact head bc4820109c66af5cae16de690bc274030c9edce9: clean and ready for human review. The rebase now includes current mains five new declared XCTest methods; the selection gate finds 209 declared methods with zero dark, the combined macOS lane executes exactly 183 host tests with zero failures, then passes replay. Coverage shards merge to the same full 1,098-file / 8,145-test union, and the 48-check gate manifest remains intact. All completed checks are green; only iOS smoke is still in progress.

Sequence remains #1996 first, then rebase/revalidate #1994 so its environment clearing applies to the consolidated job topology.

@thymikee
thymikee merged commit 02d548d into main Aug 24, 2026
20 checks passed
@thymikee
thymikee deleted the ci/optimize-checks-2 branch August 24, 2026 14:42
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-24 14:43 UTC

thymikee added a commit that referenced this pull request Aug 25, 2026
Reviewer feedback on #2036: the standalone four-case test duplicated the
harness above it and only two states are meaningful for this regression.
Reuse the same parsed workflow, temp dir, resolver stub, and Node stub;
keep neither-cached (both platforms, has-work=true) and both-cached
(empty matrix, has-work=false, release gated). Drops the single-cache
permutations, which exercise #1996's unchanged filtering rather than
this fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant