Repository navigation
ci(repo): run the release gate on main only, with readable, bounded shards - #82
Merged
Merged
Conversation
Two of three release-gate runs on #81 lost the whole ubuntu-latest VM (16 GB) mid-shard. Node's default old-space ceiling is about 4 GiB per isolate, and each test-runner child holds a main isolate plus a claimed and a spare vitest worker thread, so two runners running a mutant that allocates without bound can outgrow the VM before the 45 s mutant timeout settles them. testRunnerNodeArgs passes --max-old-space-size=2048 to every test-runner child. The flag is process-wide in V8, so each worker thread gets the same ceiling. A worker that reaches it is ended by Node; Stryker retries the mutant twice and then records it as a RuntimeError, so the run carries on. The full xstate suite passes on one thread with a 512 MiB ceiling, so 2 GiB leaves room for the coverage instrumentation.
systemfsoftware-maker
added this pull request to stack #83
October 9, 2026 18:01
On #81 the shard ends with exit 3 about 26 s into mutation testing, and the job log keeps only the first 1024 characters of the child's stderr, which are startup lines. The diagnostics re-run then ran for 16 minutes until the VM was lost, so its stderr.log never reached an artifact. A new step prints the last 32 KiB of each package's stryker.log as soon as the shard fails, before anything else runs. The re-run is bounded by timeout 600 (kill after 30 s more) and --concurrency 1, which gives one checker and one test runner, and prints the last 32 KiB of its stderr, stdout and stryker.log into the job log before the upload step.
At Stryker's default concurrency on the 4 vCPU hosted runner, a shard starts 2 checkers, each driving a native tsgo process, and 2 test runners. Run 37975117762 on #82 (main's code) and every gate run on #81 ended with exit 3 about two minutes in, or lost the 16 GB VM. The bounded diagnostics re-run of the same project at concurrency 1 passed its dry run and tested 276 mutants in five minutes without a failure. concurrency: 2 splits into one checker and one test runner. The 2 GiB test-runner heap cap stays as a second guard.
…r.log Run 37978503748 printed "(no such file)" for packages/*/stryker.log. This Stryker build accepts fileLogLevel but nothing writes the file: the option appears only in the schema, the CLI table and the fingerprint key list. The setting and every stryker.log path go. The print step now tails the shard wrapper's own progress stream (reports/mutation-stream.jsonl, the default for a run without --progressStreamFile) and each project child's stream under reports/shards. Every run writes its framed events to that file, one synced line at a time. The diagnostics artifact keeps the wrapper's stream, and the re-run prints its stderr and stdout.
`stryker run --shard` spawns one child per planned project with stdout ignored, keeps 4096 characters of its stderr and prints 1024 of them (stryker-js dist/main.mjs 113412-113483, 114204-114206). Every exit-3 run on #81 and #82 lost the error that way. The shard step now runs the same children itself: - stryker-plan-gate.ts gains a `shard` mode. It decodes the plan with the gate's ShardPlan schema and finds the shard by its index/count label, as the wrapper does (112975-112980, 113345-113362), refusing an unknown label. It writes "<index>\t<project>" lines in plan order. - .github/scripts/run-shard.sh runs each project in its own directory with the wrapper's exact args (113415-113429) and seeding (113431-113437), writing stdout.log and stderr.log beside the stream under reports/shards/<index>/<project>/. Exit 1 logs the wrapper's below-threshold line and carries on (113478); any other exit, or a missing progress stream, prints both log tails and stops the shard, as the wrapper's sequential forEach does (113474-113483). The diagnostics re-run and its artifact go: the shard artifact now carries both logs. The verdict job, merge and gate are unchanged.
Mutation testing runs on main, never on a pull request. The release gate loses its pull_request trigger and everything that existed only for it: - the plan job's changed-files step and fetch-depth 2; - the scope output, MUTATION_SCOPE and stryker.shared.ts's scopedMutate, so every enrolled file is mutated; - stryker-plan-gate.ts's change scoping (GATE_FILES, scopeOf, resolveScope, readChanged, the out-of-scope report and the MalformedStrykerConfig refusal), its --changed and --scope-out flags, and their tests; - the pull-request-only cache skip and cancel-in-progress. The verdict job, the shard layout, run-shard.sh, concurrency 2, the 2 GiB heap cap and the log tails on failure stay. The release-gate sandbox proof now finds the mutation step by run-shard.sh instead of `stryker run`. It runs the shard selection and shard steps with a stub stryker, and checks that the progress stream lands where the shard artifact uploads it. README and CONTRIBUTING describe the gate as main-only again.
Contributor
There was a problem hiding this comment.
Verified: 7/7 regular checks green on f794dde; run-shard.sh proven by run 37984176648 (shard success); no threshold/mutate-set change; pull_request trigger removed per Ryan (mutation on main only); 0 threads; hunt clean.
systemfsoftware-maker
added a commit
that referenced
this pull request
Oct 9, 2026
The guards capability no longer waits on a pull-request mutation gate. Mutation runs on main only, so this branch goes back onto main without #82's gate changes. release-gate.yml, the shard scripts, stryker-plan-gate.ts with its tests, and stryker.shared.ts return to main's content. They reach main through #82 itself.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Mutation testing runs on
mainonly, never on a pull request. #78 added apull_requesttrigger scoped to each change, and this PR takes it out again. It also keeps the fixes that makemain's gate readable and stable. Shards on the 4 vCPU, 16 GBubuntu-latestrunner were ending in exit 3 or a lost VM, and the shard wrapper hid the child's error: it ignores the child's stdout (main.mjs:113463), keeps 4,096 characters of its stderr (113472) and prints the first 1,024 of those (114204-114206).Change
Removed: everything the pull-request gate needed.
pull_requesttrigger and itscancel-in-progress.fetch-depth: 2.MUTATION_SCOPEandstryker.shared.ts'sscopedMutate, so every enrolled file is mutated.stryker-plan-gate.ts's change scoping (GATE_FILES,scopeOf,resolveScope,readChanged, the out-of-scope report, theMalformedStrykerConfigrefusal), its--changedand--scope-outflags, and their tests.The gate runs on push to
mainand onworkflow_dispatch. Apart from the shard selection mode,stryker-plan-gate.tsand its tests are back to their pre-#78 form.Kept: readable, bounded shards.
stryker-plan-gate.ts sharddecodes the plan with the gate'sShardPlanschema and finds the shard by itsindex/countlabel, as the wrapper does (main.mjs:112975-112980,113345-113362). It refuses an unknown label and writes<index>\t<project>lines in plan order..github/scripts/run-shard.shthen runs each project from its own directory with the wrapper's exact args (113415-113429) and incremental seeding (113431-113437). It writesstdout.logandstderr.logbeside the progress stream underreports/shards/<index>/<project>/, which the shard artifact already uploads.113385-113391,113478). Any other exit prints the tails of both logs and stops the shard with that exit code. A child that leaves no progress stream prints both tails and stops with 3, the wrapper's RuntimeError code (113479,83447). The wrapper runs projects in sequence and stops at the first failure (113474-113483,concurrency: 1). It exits 3 for any aborted child, while this step passes the child's own code through.fileLogLevelis removed. This build accepts it, but nothing writesstryker.log(65208,88970,113889,113985).concurrency: 2gives 1 checker and 1 test runner (104850-104853).testRunnerNodeArgs: ['--max-old-space-size=2048']caps each test-runner isolate (106954-106960).stryker mergeandstryker gate, apart from the no-shards message, which no longer mentions a change's scope.Sandbox proof.
sandbox-proofs/release-gate.test.tsused to find the mutation step bystryker run, which the direct-run change removed. It now finds that step byrun-shard.sh. It runs the shard selection step and the shard step in the sandbox with a stubstryker, and checks that the guard's variables reachstryker, thatALLOW_LOCAL_MUTATIONnever does, and that the progress stream lands at<plan dir>/<SHARD_OUT_MARKER>/<index>/<project>/, where the shard artifact uploads it.Smoke test with a stub
strykerin a scratch tree (two projects, nothing committed):a's incremental file was seeded;a: the line was logged,bstill ran and the step exited 0;a: the step exited 3 and printeda's stderr ("Initial test run failed. 1 of 2 test(s) failed: flaky") and stdout (the exact child argv);bdid not run;b: the step exited 3 with both tails.shard --shard 1/1on run 37970427513's real plan prints1\tpackages/xstate.Gate ownership
This edits a judgment surface. The conductor, who owns the release gate, asked for these changes in cycles 247 to 264, and the main-only direction is Ryan's. No threshold or mutant timeout changes. The mutated set grows back to every enrolled file.
Gates
dprint check,actionlint,tsc -p tsconfig.node.json --noEmit,oxlint,deno checkanddeno lintall exit 0.stryker-plan-gate.test.tspasses 11 of 11.sandbox-proofs/release-gate.test.tspasses 5 of 5.git grepfinds none ofMUTATION_SCOPE,scopedMutate,scopeOf,resolveScope,readChanged,changed-files,GATE_FILES,renderOutOfScopeorMalformedStrykerConfigoutsiderepos/.main's next gate run after merge, which must pass the verdict.