Diagnosed after gc-ratchet and gc-root-dominance failed identically on
#9827, #9823, #9816 and #9807 — branches touching unrelated code. Unrelated
changes cannot produce identical failures, so the question was whether main
fails its own ratchet with no PR involved. It does.
1. Main fails its own ratchet
The workflow's main-line arm is a six-hourly schedule (push: branches: [main] was removed in #7856 because it starved the queue). That arm is red:
09-05T20:15 schedule failure d36a1af0c
09-05T15:05 schedule failure c7361c87c
09-05T10:28 schedule failure 1d63fa91f
Run 33989581881 (scheduled, main @ d36a1af0c, no PR) fails with 43
regression rows.
2. The PRs inherit it exactly — they contribute nothing
Extracting the regression rows from main's own scheduled run and from PR
#9823's run:
$ diff main_rows.txt pr9823_rows.txt
(no output — all 43 rows byte-for-byte identical)
So the campaign's GC-adjacent work is not the cause, and re-pinning on any
of those PRs would be absorbing main's problem into a branch that did not
create it.
3. It started 2026-08-18 and has been red for 19 days
Every scheduled run since is red; the last green is dc4bcf287:
08-18T01:46 failure 7441e1f73 <- first red
08-17T18:52 success dc4bcf287 <- last green
Of the last 100 scheduled runs (08-11 → 09-05): 15 success — all of them on
or before 08-17 — 64 failure, 16 cancelled.
4. It is NOT one regression: the failure has grown 19 -> 43 cells
The first red run (7441e1f73) had 19 regression cells. Today's has 43.
14 have been red the whole time; 29 were added since, including whole
probes that were clean on 08-18:
01_nursery_churn copied_bytes, copied_objects
04_dead_after_deep_stack copied_bytes, promoted_objects
05_closure_capture copied_bytes, promoted_bytes, promoted_objects
06_string_retention copied_bytes, copied_objects, freed_bytes, promoted_*
07_array_grow_evacuate copied_objects, heap_total_bytes, heap_used_bytes
09_try_catch_roots copied_bytes, copied_objects, promoted_bytes, promoted_objects
10_store_receiver_across_alloc copied_bytes, copied_objects, freed_bytes, heap_used_bytes
11_collect_at_depth copied_bytes, copied_objects
13_large_eden_survivors copied_bytes, copied_objects, heap_total_bytes, promoted_objects
At least two accrual events, 19 days apart. That is the single most
important fact here, because it rules out a one-line re-pin.
Representative rows from today (main, scheduled):
| probe |
counter |
baseline |
current |
delta |
10_store_receiver_across_alloc |
heap_used_bytes |
220,384 |
464,072 |
+110.57 % |
10_store_receiver_across_alloc |
copied_objects |
8,160 |
0 |
−100 % |
10_store_receiver_across_alloc |
freed_bytes |
8,930,928 |
0 |
−100 % |
09_try_catch_roots |
promoted_objects |
6,240 |
0 |
−100 % |
13_large_eden_survivors |
copied_objects |
0 |
377,569 |
— |
12_large_live_set |
freed_bytes |
76,315,456 |
63,732,784 |
−16.49 % |
The shape — byte counters moving together, and some probes going to zero
copied/promoted/freed — reads as probes whose collection schedule changed
(they stopped triggering a minor at all), not as a retention leak.
5. Candidate for the FIRST accrual, offered as a lead not a conclusion
Ten commits sit between the last green and the first red:
7441e1f73 feat(native): expose u8 and byte profile types (#8330)
2775ffe21 runtime: add zero-copy bun:ffi memory views (#6562) (#8329)
765900869 fix(worker_threads): preserve channel listener semantics (#8327)
1ed959122 ci: shard parity 8 ways (#8244)
6da121d02 test(ci): close out #8264 (#8335)
3f2fffc4f fix(dyn_eval): root global this before env_new_root allocates (#8333)
de70a433e fix(tls): match Node server identity semantics (#8334)
e3e0105e4 fix(node:test): expose test context assertions (#8332)
770b6223d chore(make): simplify fix target to clippy --fix (#8331)
e2e6f4a1c perf(object): shrink common objects to 40 bytes (#8313)
e2e6f4a1c — "shrink common objects to 40 bytes" — is the obvious
candidate: changing the common object size moves every byte-derived counter
and, by changing how fast the nursery fills, the collection schedule too, which
is exactly the failure shape. It is also plausibly a win that simply needs
the baseline re-pinned with that justification. I have not bisected it and I
am not asserting it. The second accrual is unattributed.
6. Why nobody saw it — the same class as #9774 and #7856
- The PR arm is opt-in on
run-extended-tests, so almost every PR shows
skipping and proves nothing.
- The main arm is a schedule, and a scheduled failure blocks nothing and
notifies nobody.
So main can drift past its own pinned baseline indefinitely, and the first PR
to ask for the gate inherits 19 days of accumulated red. A gate that has failed
64 of its last 100 runs while blocking nothing is dark in CLAUDE.md's sense —
a fourth way, alongside continue-on-error, missing-from-required-contexts,
cancelled and starved: failing but unwatched.
7. Do NOT re-pin to clear this
Re-pinning is how a real regression gets absorbed, and here it would absorb an
unknown number of them: 43 cells, at least two accrual events, 19 days and
~200 commits. Nobody can currently say which of those values are correct.
Note 423975f6f (Sep 5) touched the baseline file but changed
probe_overrides / rationale metadata only — no one has re-pinned counter
values, so this red is not a hidden re-pin returning.
Suggested order of work
- Bisect the first accrual across the 10 commits above (the ratchet can be
run on a single commit; #8313 first).
- Bisect the second, which lands somewhere in 08-18 → 09-05.
- Only then re-pin, per-cell, with the justification the gate's own docs
demand: which counters moved, why the new values are correct, and what would
have failed under the old pin.
- Make the scheduled arm visible — a red six-hourly
main run should open
or update an issue automatically. That is the part that would have saved 19
days, and it is independent of whatever the counters turn out to mean.
Filed from the keystroke lane while unblocking #9807/#9823/#9828; happy to take
the bisection if it is wanted, but which counter values are correct is a
judgement I should not make alone.
https://claude.ai/code/session_014UZWia6L37DpA93VLtNK9m
Diagnosed after
gc-ratchetandgc-root-dominancefailed identically on#9827, #9823, #9816 and #9807 — branches touching unrelated code. Unrelated
changes cannot produce identical failures, so the question was whether main
fails its own ratchet with no PR involved. It does.
1. Main fails its own ratchet
The workflow's main-line arm is a six-hourly schedule (
push: branches: [main]was removed in #7856 because it starved the queue). That arm is red:Run
33989581881(scheduled,main@d36a1af0c, no PR) fails with 43regression rows.
2. The PRs inherit it exactly — they contribute nothing
Extracting the regression rows from main's own scheduled run and from PR
#9823's run:
So the campaign's GC-adjacent work is not the cause, and re-pinning on any
of those PRs would be absorbing main's problem into a branch that did not
create it.
3. It started 2026-08-18 and has been red for 19 days
Every scheduled run since is red; the last green is
dc4bcf287:Of the last 100 scheduled runs (08-11 → 09-05): 15 success — all of them on
or before 08-17 — 64 failure, 16 cancelled.
4. It is NOT one regression: the failure has grown 19 -> 43 cells
The first red run (
7441e1f73) had 19 regression cells. Today's has 43.14 have been red the whole time; 29 were added since, including whole
probes that were clean on 08-18:
At least two accrual events, 19 days apart. That is the single most
important fact here, because it rules out a one-line re-pin.
Representative rows from today (main, scheduled):
10_store_receiver_across_alloc10_store_receiver_across_alloc10_store_receiver_across_alloc09_try_catch_roots13_large_eden_survivors12_large_live_setThe shape — byte counters moving together, and some probes going to zero
copied/promoted/freed — reads as probes whose collection schedule changed
(they stopped triggering a minor at all), not as a retention leak.
5. Candidate for the FIRST accrual, offered as a lead not a conclusion
Ten commits sit between the last green and the first red:
e2e6f4a1c— "shrink common objects to 40 bytes" — is the obviouscandidate: changing the common object size moves every byte-derived counter
and, by changing how fast the nursery fills, the collection schedule too, which
is exactly the failure shape. It is also plausibly a win that simply needs
the baseline re-pinned with that justification. I have not bisected it and I
am not asserting it. The second accrual is unattributed.
6. Why nobody saw it — the same class as #9774 and #7856
run-extended-tests, so almost every PR showsskippingand proves nothing.notifies nobody.
So main can drift past its own pinned baseline indefinitely, and the first PR
to ask for the gate inherits 19 days of accumulated red. A gate that has failed
64 of its last 100 runs while blocking nothing is dark in CLAUDE.md's sense —
a fourth way, alongside
continue-on-error, missing-from-required-contexts,cancelled and starved: failing but unwatched.
7. Do NOT re-pin to clear this
Re-pinning is how a real regression gets absorbed, and here it would absorb an
unknown number of them: 43 cells, at least two accrual events, 19 days and
~200 commits. Nobody can currently say which of those values are correct.
Note
423975f6f(Sep 5) touched the baseline file but changedprobe_overrides/rationalemetadata only — no one has re-pinned countervalues, so this red is not a hidden re-pin returning.
Suggested order of work
run on a single commit;
#8313first).demand: which counters moved, why the new values are correct, and what would
have failed under the old pin.
mainrun should openor update an issue automatically. That is the part that would have saved 19
days, and it is independent of whatever the counters turn out to mean.
Filed from the keystroke lane while unblocking #9807/#9823/#9828; happy to take
the bisection if it is wanted, but which counter values are correct is a
judgement I should not make alone.
https://claude.ai/code/session_014UZWia6L37DpA93VLtNK9m