Skip to content

gc-ratchet has been RED on main since 2026-08-18 (19 days); every labelled PR inherits it, and the failure has grown from 19 to 43 cells #9829

Description

@proggeramlug

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

  1. Bisect the first accrual across the 10 commits above (the ratchet can be
    run on a single commit; #8313 first).
  2. Bisect the second, which lands somewhere in 08-18 → 09-05.
  3. 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.
  4. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions