Skip to content

Promote gc-ratchet to a required context now (precondition met); gc-stress after #8100 #8101

Description

@proggeramlug

Split out of #7737 item 4, which is closed as three-quarters done. This is the
remaining quarter, and the picture has moved twice since #7737 was filed —
in both directions — so it is worth stating the current one precisely.

What is true right now

gh api repos/PerryTS/perry/branches/main/protection --jq .required_status_checks.contexts

["lint","cargo-test","parity","compile-smoke","api-docs-drift","security-audit","conformance-smoke-complete"]

Neither gc-ratchet nor gc-stress is there. Both run, both can report red, neither
blocks a merge — CLAUDE.md's "four ways a gate can be unable to fail", hazard 2.

gc-ratchet: the precondition is now MET — promote it

CLAUDE.md's corollary is "a new gate has never been green, so promoting it to
required immediately blocks every open PR. Run it once, then promote." On
2026-08-12 that was the blocker: gc-ratchet had 0 successes in its last 30 main
runs. That is no longer the case. Its last four main runs are all green, on the
four consecutive main commits immediately preceding 0d7fe21b0:

run when (UTC+2) main sha conclusion
31766259491 2026-08-14 03:15 f9475fb9a success
31781155927 2026-08-14 07:45 78d0afa46 success
31805278312 2026-08-14 13:33 5c27d1ad9 success
31831865551 2026-08-14 19:07 506f4ab11 success

The flip is attributable: f9475fb9a is fix(gc): attribute deterministic ratchet shifts (#8069), the last touch of baseline/gc-ratchet-v1.json, and the first green
run is the one at that commit. #8051 — the attribution blocker the previous triage
named — is closed. So is #7966.

The green is not vacuous — the gate's subject ran. Run 31831865551 measured all
fourteen probes and ended gc-ratchet: OK; e.g.

14_grow_then_churn: correctness=pass heap_used_bytes=0% heap_total_bytes=0% rss_bytes=0.075594% peak_rss_bytes=0.080871% wall_ms=17.099233%
12_large_live_set:  correctness=pass heap_used_bytes=0% heap_total_bytes=0% rss_bytes=0.8705%   peak_rss_bytes=0.857633% wall_ms=19.992527%

12_large_live_set's heap_used_bytes / heap_total_bytes cells are the exact cells
#7737 said were advisory-only, and they are gating rows with a 0% delta today.

Admin action, once:

gh api -X PATCH repos/PerryTS/perry/branches/main/protection/required_status_checks \
  -f 'contexts[]=lint' -f 'contexts[]=cargo-test' -f 'contexts[]=parity' \
  -f 'contexts[]=compile-smoke' -f 'contexts[]=api-docs-drift' \
  -f 'contexts[]=security-audit' -f 'contexts[]=conformance-smoke-complete' \
  -f 'contexts[]=gc-ratchet'

Caveat worth weighing before pressing it: gc-ratchet.yml fires on schedule +
push, and this repo's push: on the test workflows is tags-only. Check that the
context actually reports on pull requests before making it required, or every PR
will sit forever on a check that never arrives — that is a different way to block
every open PR than the one CLAUDE.md warns about, with the same effect. Confirm on
one PR first.

gc-stress: still red, and for exactly ONE reason — see the linked bug

Eight consecutive nightly test.yml runs on main, 2026-08-07 through 2026-08-14,
all report gc-stress=failure (31150713440, 31240304595, 31295652054,
31358183869, 31460223673, 31566481914, 31670425699, 31772993217).

The most recent one narrows to a single row of scripts/gc_repsel_matrix.sh:

summary: PASS=1230 UNVER=310 XFAIL=0 FAIL=22
specabi_reassign   FAIL FAIL FAIL ... FAIL      <- 22 of 22 arms, INCLUDING shipped_default

Every other row is PASS or UNVER. It is not a GC-arm failure: it fails with
cycles=0 evacuated=0 scavenged=0 reclaimed=0 in several arms, it fails under
shipped_default requires=none, and every repsel kill switch leaves it broken.

Filed as #8100 with a five-line reproducer and a root cause: element reads on a
binding whose initializer was new Int32Array(...) lower to an unguarded
js_typed_array_get, and that helper answers 0.0 when clean_ta_ptr rejects the
receiver instead of falling back to the ordinary [[Get]]. Fixing #8100 is the whole
of what stands between gc-stress and a green main run.

There was also a SECOND, now-resolved cause worth recording so nobody re-derives it:
the 2026-08-08/09 runs (31240304595, 31295652054) had FAIL=0 in the matrix and
were red on the liveness gate instead — four arms (default, verify_evac,
cons_scan_off, cons_scan_off_force) flagged STALE-REGISTRY, listed in
scripts/gc_matrix_inert_arms.txt as known-inert while actually satisfying
requires=scavenge on 41/58 cells. That is gone in the 2026-08-14 run: every arm now
prints live.

Ordering

  1. Promote gc-ratchet now (precondition met), after confirming it reports on a PR.
  2. Fix Reassigning a new Int32Array binding to a plain array makes every element READ return 0 (silent miscompile; sole reason gc-stress is red) #8100 (specabi_reassign).
  3. Observe gc-stress green on main once.
  4. Promote gc-stress.

Note also #8092: lint and cargo-test are currently red for every PR, so the
required-context set is already being bypassed at merge time. Promoting a gate into a
set that is routinely bypassed buys less than it looks like it does; worth resolving
#8092 alongside.

Activity

  1. proggeramlug commented on Aug 14, 2026

    @proggeramlug
    ContributorAuthor

    Independently verified both halves of the precondition. gc-ratchet is ready to promote; gc-stress is not, for exactly the reason stated.

    gc-ratchet — green on main AND on PRs

    CLAUDE.md's rule is "run it once, then promote", because a gate that has never been green blocks every open PR the moment it becomes required. Both directions check out:

    main, last eight runs — four consecutive successes, immediately after four failures, so the flip is attributable rather than incidental:

    success  506f4ab11  08-14T19:07
    success  5c27d1ad9  08-14T13:33
    success  78d0afa46  08-14T07:45
    success  f9475fb9a  08-14T03:15     <- #8069 lands here
    failure  0da668c95  08-14T02:38
    failure  3381e1b89  08-13T19:15
    failure  971d6ffb6  08-13T13:38
    failure  81a88de40  08-13T07:47
    

    And the caveat this issue itself raises — does the context actually report on a pull request — holds. Four recent PRs, all SUCCESS:

    PR 8086  gc-ratchet=SUCCESS
    PR 8090  gc-ratchet=SUCCESS
    PR 8091  gc-ratchet=SUCCESS
    PR 8087  gc-ratchet=SUCCESS
    

    That was worth checking separately: a job can be green on main and still never produce a check run on a PR, which is precisely the state parity and compile-smoke are in today (they report SKIPPED). gc-ratchet is not in that state.

    gc-stress — correctly blocked on #8100

    PR 8086  gc-stress=FAILURE
    PR 8090  gc-stress=FAILURE
    PR 8091  gc-stress=CANCELLED
    PR 8087  gc-stress=CANCELLED
    

    Consistent with the single-cause diagnosis: specabi_reassign fails 22 of 22 matrix arms including shipped_default. An agent is on #8100 now. Promoting gc-stress before that lands would make every PR unmergeable, which is the opposite of the point.

    What I am not doing

    Editing branch protection is an outward-facing, hard-to-reverse repository configuration change that affects every contributor, so I am not making it unilaterally — surfacing it to the maintainer with the evidence above instead.

    Worth sequencing against #8092 when it happens: the required list currently contains two contexts that report SKIPPED on every PR (parity, compile-smoke — informational by design since v0.5.1018, both continue-on-error: true) and two that are red for everyone (cargo-test, conformance-smoke-complete, the latter failing 8 of 8 shards). Adding a genuinely-green gate to that list is good; it would be better still alongside removing the two that cannot be satisfied.

  2. proggeramlug commented on Aug 15, 2026

    @proggeramlug
    ContributorAuthor

    Maintainer decision: fix the existing required set first

    Holding this promotion. The precondition I verified is genuinely met — gc-ratchet is green on main for four consecutive runs after four failures, and reports SUCCESS on four separate PRs, so unlike parity/compile-smoke it actually runs pre-merge. That is not in question.

    The reason to wait is what #8117 established since: the required set is currently unsatisfiable. conformance-smoke fails 8 of 8 shards for ten real reasons, and cargo-test fails on four ELF-only byte-identity tests. Adding a genuinely-green gate to a list that no PR can satisfy does not protect anything — every merge still goes through admin bypass, which is the habit that let #8100 (a silent wrong-answer miscompile in the shipped default) sit unnoticed for days behind a cancelled shard.

    So the sequence is:

    1. Clear the required set. conformance-smoke fails 8/8 shards: 12 real regressions, 10 newly-visible node_fail transitions, 1 fix the snapshot hasn't accepted #8117 tracks ten remaining regressions, now down from twelve — array_methods fixed by fix(array): ask the typed-array question before clean_arr_ptr rejects it, for sort/toSorted/toReversed/with #8119 and specabi_reassign by fix(typedarray): dispatch, not just validate, when the element-read receiver is not a typed array #8109. Two of the ten are crashes, two are ordering-dependent (do not bisect those before establishing isolation), and two look like one bug.
    2. Then promote gc-ratchet, at which point required means something again.
    3. gc-stress after Reassigning a new Int32Array binding to a plain array makes every element READ return 0 (silent miscompile; sole reason gc-stress is red) #8100's family fully clears — it was blocked on exactly that and fix(typedarray): dispatch, not just validate, when the element-read receiver is not a typed array #8109 has landed part of it.

    Worth deciding alongside step 2: parity and compile-smoke are in the required list while reporting SKIPPED on every PR by design (tag-gated since v0.5.1018, both continue-on-error: true). Removing them would leave a required list containing only contexts that actually run pre-merge, which is the property that makes the list mean anything.

    Nothing here is lost — the verification stands and the promotion is a one-command change once the set is clean.

  3. proggeramlug commented on Aug 15, 2026

    @proggeramlug
    ContributorAuthor

    Status: the gc-ratchet case is sound, and the thing blocking it just got much smaller

    Not promoting yet — the owner's call on this was "fix the existing required set first", and promoting a new context into a set that is already unsatisfiable would just add another forced admin bypass. Recording where that stands, because it moved a lot today.

    The blocker was conformance-smoke-complete, and it is now 3 failures, not 12

    conformance-smoke-complete is in the required set and is red on main — which is why every merge today went in under --admin. That is #8117.

    I re-ran all twelve of #8117's pass -> X regressions against current main (fb87d8923) on a fresh perry-dev build, byte-compared against node 26.5.1. Nine now pass. All three pass -> crash entries are gone — two were a single bug (a missing Buffer/DataView arm in obj_value_has_own_key, #8141) and specabi_reassign was #8100/#8109. The remainder cleared with #8090/#8109/#8119/#8120/#8134/#8135. Full table in #8117.

    Three remain, and they are in flight:

    • test_gap_collection_foreach_member_receiver_thisarg
    • test_gap_set_map_foreach_fused_receiver (almost certainly the same bug as the above)
    • test_gap_sso_concat_string_index (string indexing returns undefined after an SSO concat — the worst of the three)

    Caveat I want on the record: my harness compiles and cmps directly, while the real gate applies normalize_output. So the nine are "very likely clear" rather than proven clear, and the next full conformance-smoke run on main is what should be trusted. I would rather flag that than have the required set declared satisfiable on my numbers and find it still red.

    gc-ratchet's own precondition is still met

    Nothing here changes this issue's argument. The four consecutive green main runs stand, the flip is attributable to #8069, and the green is non-vacuous — run 31831865551 measured all fourteen probes and reported per-probe deltas, so the subject demonstrably ran rather than the job merely not throwing. That is the check CLAUDE.md's hazard 4 asks for, and it passes.

    Sequence

    1. Land the three fixes above.
    2. Confirm conformance-smoke-complete goes green on a real main run — not on my local recount.
    3. Then promote gc-ratchet into the required contexts, at which point it costs nothing because the set is satisfiable again.

    Worth noting for step 3: parity and compile-smoke are also in the required set but are tag-gated and never run on a PR, so they can never report success there. Whatever "satisfiable" means operationally, it has to account for those two as well — otherwise promotion is blocked on a condition that cannot be met regardless of #8117. That is a separate decision from this issue's, but it lands in the same place, so someone should make it deliberately rather than discover it at promotion time.

  4. proggeramlug commented on Aug 16, 2026

    @proggeramlug
    ContributorAuthor

    Re-checked today: the window opened, and then closed again a few hours ago

    Short version: everything this issue asked for as a precondition is now true except one thing that broke today, and that one thing has a fix already open. Not promoting yet, and here is the checkable reason rather than a judgement call.

    The two side-conditions from the earlier comments are resolved

    gh api repos/PerryTS/perry/branches/main/protection --jq .required_status_checks.contexts

    ["lint","cargo-test","api-docs-drift","security-audit","conformance-smoke-complete"]
    

    parity and compile-smoke are gone — that was the "remove the two contexts that report SKIPPED on every PR by design" recommendation from the 2026-08-15 comments, and it has since been done. #8117 is closed. #8100 is closed.

    gc-stress: this issue's step 3 is satisfied

    The gc-stress job is green on the last two nightly main runs, after eight consecutive failures:

    31926695806  0eccf7929  2026-08-16T04:27  gc-stress=success
    31864245512  83b6b8c69  2026-08-15T04:22  gc-stress=success
    31772993217  601a02d23  2026-08-14T05:26  gc-stress=failure
    31670425699  59cf9d9c9  2026-08-13T05:28  gc-stress=failure
    31566481914  c4b2c1c8e  2026-08-12T05:25  gc-stress=failure
    

    Consistent with the single-cause diagnosis: specabi_reassign was #8100, closed by #8109. Worth recording alongside it that the job-level continue-on-error this issue's hazard-1 concern was about is gone — the only continue-on-error: true left in the gc-stress block is step-level, on the deliberately-informational write-barrier stress hunt (~200s nondeterministic corruption-window hunts (#5029)). The job itself can fail and does report failure.

    gc-ratchet: the streak was 11 long, and #8204 ended it this afternoon

    The run of green main runs extended well past the four this issue was filed on — eleven consecutive, f9475fb9a (2026-08-14) through ba2ecaacd (2026-08-16T12:56). Then:

    bf8fd868e  perf(gc, codegen): recover the instruction cost of the 56 B -> 48 B header shrink (#8122) (#8204)
    

    bf8fd868e is not an ancestor of ba2ecaacd — it landed after the last gc-ratchet main run, and it full-re-pinned benchmarks/gc_ratchet/baseline/gc-ratchet-v1.json. A full re-pin correctly ships no accepted_deterministic_deltas receipt (that key is the audit trail for a selective re-pin), but three of the ratchet's own self-tests hard-subscript it. Checked against origin/main content directly rather than inferred from CI:

    $ git show origin/main:benchmarks/gc_ratchet/baseline/gc-ratchet-v1.json | \
        python3 -c "import json,sys; print('accepted_deterministic_deltas' in json.load(sys.stdin))"
    False
    
    $ python3 -m unittest tests.test_gc_ratchet      # origin/main tree
    ERROR: test_selective_refresh_names_every_accepted_cell_and_cause    KeyError: 'accepted_deterministic_deltas'
    ERROR: test_selective_refresh_receipt_cannot_disagree_with_the_pin   KeyError: 'accepted_deterministic_deltas'
    ERROR: test_selective_refresh_receipt_rejects_a_malformed_timestamp  KeyError: 'accepted_deterministic_deltas'
    Ran 97 tests
    

    So gc-ratchet is red on main HEAD, and red on every branch based at or after bf8fd868e — observed on #8203, #8209, #8210 (e.g. run 31953454021). Promoting it right now would make every open PR unmergeable, which is exactly the CLAUDE.md corollary this issue quotes in its own opening.

    The part worth flagging beyond the immediate breakage: main's green record does not show this yet, because no gc-ratchet run has executed on a commit at or after bf8fd868e. The last-N-runs check that this issue and its first comment both used as the promotion test would have read "eleven green, promote" at any point today and been wrong. A freshness check on the commit the streak covers, not just the count, is what distinguishes those two states.

    #8214 is the fix and is open, test-only, and sabotage-tested. It keeps the validator tests (rebuilding their fixture synthetically instead of assuming the pin carries a receipt), drops #8069's 21 frozen cells, and adds the case nothing covered: a full re-pin with no receipt is valid.

    One new consideration that did not exist when this was filed

    #8187 (ci: restructure CI into PR gate / main sweep / full suite tiers) is open, and on that PR gc-ratchet reports skipping. If gc-ratchet becomes required and #8187 then lands, the context becomes a skipped check — which branch protection treats as a pass. That is hazard 4 wearing yet another hat: required, always green, subject never ran. Whichever of the two lands first, the other needs a deliberate re-check rather than an assumption.

    Updated ordering

    1. Land test(gc-ratchet): stop the self-tests demanding a selective re-pin receipt #8214. (Blocks everything else here; also unbreaks windows-build on every open PR.)
    2. Observe one gc-ratchet main run green on a commit at or after that merge — the commit, not the count.
    3. Promote gc-ratchet, sequenced deliberately against ci: restructure CI into PR gate / main sweep / full suite tiers #8187 so it does not become a skipped-and-therefore-passing context.
    4. Promote gc-stress — its own precondition is already met (two green nightlies, Reassigning a new Int32Array binding to a plain array makes every element READ return 0 (silent miscompile; sole reason gc-stress is red) #8100 closed).

    Still true that #8092 is open: cargo-test is red on the nightly full-workspace main run (31926695806), so the required set is not yet satisfiable end-to-end, and merges continue to bypass. That does not block steps 1-2, and it is the reason not to treat step 3 as urgent.

  5. proggeramlug commented on Aug 16, 2026

    @proggeramlug
    ContributorAuthor

    gc-ratchet is now a required context

    #8214 merged as 2e7a12818 (current main HEAD), which clears the breakage recorded in the previous comment. Verified before pressing, in both directions:

    The self-tests actually pass on the new tree — not inferred from CI:

    $ python3 -m unittest tests.test_gc_ratchet      # origin/main @ 2e7a12818
    Ran 98 tests in 8.719s
    OK (skipped=2)
    

    And a real main run on that exact commit is green, non-vacuously. gc-ratchet sweeps main on a six-hourly cron rather than per-merge, so rather than wait for the next window I dispatched run 31960548698 against 2e7a12818. All fourteen probes measured and the checker rendered a verdict — hazard 4, "a gate must assert its subject was live":

    01_nursery_churn:      correctness=pass heap_used_bytes=0% heap_total_bytes=0% rss_bytes=0.341297% ...
    12_large_live_set:     correctness=pass heap_used_bytes=0% heap_total_bytes=0% rss_bytes=0.040328% ...
    14_grow_then_churn:    correctness=pass heap_used_bytes=0% heap_total_bytes=0% rss_bytes=0.108733% ...
    gc-ratchet: OK
    

    Promoted by append (the branch is strict: false, so nothing else in the object moved):

    gh api -X POST repos/PerryTS/perry/branches/main/protection/required_status_checks/contexts \
      -f 'contexts[]=gc-ratchet'
    ["lint","cargo-test","api-docs-drift","security-audit","conformance-smoke-complete","gc-ratchet"]
    

    Reverting is symmetric — -X DELETE on the same endpoint with the same field.

    Two things that came out of doing it, both worth acting on separately

    1. The queue, not the job, is now the cost. The dispatched run waited 60 minutes for a macOS runner (17:05 → 18:06 UTC) and then executed in 14. Every PR-side gc-ratchet run started in the hour after #8214 merged is still queued or was cancelled by a superseding push — there is not yet a single completed PR-side run on post-#8214 code. So the practical merge cost of this context is ~75 minutes, not ~14, and it is now merge-blocking rather than merely advisory. That is #7856's starvation showing up on the PR lane instead of the main lane; docs/src/testing/ci-gate-scheduling.md is the writeup, and this promotion converts it from an invisible problem into a felt one. If PR-side runs keep getting starved, the honest fix is cutting total job demand (which is what #8187 is for), not reverting this.

    2. Sequence against #8187 deliberately. On #8187 this context reports skipping, and branch protection treats a skipped required check as a pass. If #8187 lands as-is now that gc-ratchet is required, the gate becomes required-and-always-green — hazard 4 again, in the form this issue was filed to prevent. Whoever merges #8187 needs to re-check that gc-ratchet still produces a real conclusion on PRs, rather than assume it.

    gc-stress: precondition met, deliberately NOT promoted

    Its technical case is now clean — two consecutive green nightly main runs (31864245512 @ 83b6b8c69, 31926695806 @ 0eccf7929) after eight failures, #8100 closed by #8109, and no job-level continue-on-error (the surviving one is step-level, on the deliberately-informational write-barrier hunt).

    What is not clean is the cost. gc-stress is a test.yml job with a 90-minute timeout, and at this repo's merge cadence its PR runs are routinely unfinished at merge time — cancelled or queued on #8207, #8211, #8212 and #8216. Requiring it means every merge waits on a full test.yml run. That is a scheduling decision, not a correctness one, and it should be made explicitly rather than as a side effect of this issue. Leaving this open for that call.

  6. proggeramlug commented on Aug 16, 2026

    @proggeramlug
    ContributorAuthor

    Closing: superseded by #8187's tiering

    gc-ratchet was promoted into the required contexts earlier today on a verified precondition (#8214 merged as 2e7a12818; dispatched run 31960548698 measured all fourteen probes on that exact commit and reported gc-ratchet: OK). That promotion has been reverted, and this issue is closed as superseded — because #8187 merged at 2026-08-16T18:03:02Z, roughly an hour later, and changed the premise the whole issue rests on.

    Why the promotion stopped meaning anything

    #8187 moves every satellite gate's PR arm behind an opt-in label. gc-ratchet.yml's job now reads:

    if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-extended-tests')

    So on an unlabeled PR the check run is created with conclusion skipped, which branch protection accepts. Verified on #8242 and #8215 — both show gc-ratchet=skipped, and on #8242 gh pr checks --required lists gc-ratchet as skipping.

    That is a required context that is green without running: CLAUDE.md hazard 4, produced by this issue's own remedy. A gate that cannot fail is worse than an advisory one, because the required list is the thing people trust. Removing it is the honest state:

    gh api -X DELETE repos/PerryTS/perry/branches/main/protection/required_status_checks/contexts \
      -f 'contexts[]=gc-ratchet'

    Nothing about the gate's actual coverage changed by removing it: the six-hourly main sweep arm is untouched, it still fails loudly, and gate-freshness.yml is the detector for it going dark — which, after #7856, is the property that actually mattered.

    gc-stress: deliberately not promoted

    Recording this as a decision rather than an omission. Its technical precondition is met — two consecutive green nightlies (31864245512 @ 83b6b8c69, 31926695806 @ 0eccf7929) after eight failures, #8100 closed by #8109, and no job-level continue-on-error.

    It is not being promoted because #8187 gives a better answer than a standalone required context: gc-stress now runs as a PR subset in the PR tier and full in the sweep tier, fanning into pr-gate. Requiring the old standalone job name on top of that would mean every merge waits on a 90-minute job whose PR runs were already routinely cancelled at merge time (#8207, #8211, #8212, #8216).

    What replaces this issue

    #8187's design statement is "pr-gate is the only required context." Making the required list match that — and removing the contexts the new tiering no longer emits under those names — is the remaining work, and it belongs to #8092, which is open and is precisely about the required set not being satisfiable. Worth noting there that the required list has already drifted during today's transition and currently reads ["lint","cargo-test","security-audit"] with no pr-gate in it.

    Both halves of this issue are now settled: gc-ratchet promoted, measured, and correctly reverted; gc-stress decided against with the reason on the record.

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

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions