Repository navigation
fix(partners): keep shuffle index in bounds - #9322
Conversation
Pin Date.now to the times of real build failures and to the edges of their 5-minute shuffle windows. These tests fail until shuffle is fixed: the same code passes just outside each window and fails inside it. Refs: nodejs#9321 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dividing a hash byte by 255 gives exactly 1 when the byte is 255, making `j` equal to `i + 1`. On the first pass of the loop that is one past the end of the array, so `undefined` is swapped in and crashes prerendering. The partner shuffle seed changes every 5 minutes, so this failed builds only during about 1 in 256 of those windows, whatever the code being built. Dividing by 256 keeps `j` within `[0, i]`. Fixes: nodejs#9321 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9322 +/- ##
==========================================
+ Coverage 86.97% 87.14% +0.17%
==========================================
Files 88 90 +2
Lines 6378 6426 +48
Branches 386 390 +4
==========================================
+ Hits 5547 5600 +53
+ Misses 827 822 -5
Partials 4 4 ☔ View full report in Codecov by Harness. |
📦 Build Size ComparisonSummary
|
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues are reported for the shuffle bounds correction and accompanying regression tests.
No open issues 🎉
What changed in this PR
Fixes the shuffle off-by-one behind the intermittent partner-page prerender failures reported in #9321. Dividing hash bytes by 256 keeps the generated index within bounds and prevents undefined entries.
Changes:
- Corrects hash-byte normalization to produce values in
[0, 1). - Adds shuffle tests for determinism, input preservation, and permutation integrity across seeds and list sizes.
- Adds partner-generator regression tests using mocked failure timestamps and five-minute window boundaries.
| File | Description |
|---|---|
apps/site/util/array.ts |
Keeps shuffle indices within array bounds. |
apps/site/util/__tests__/array.test.mjs |
Tests shuffle invariants and known failing seeds. |
apps/site/next-data/generators/__tests__/partners.test.mjs |
Verifies partner-list integrity across historical failure windows. |
Review effort: Balanced
|
If you need to fast-track, you have my 👍, @bmuenzenmeyer! |
|
Thanks - I'd rather have this than keep allowing random failures confuse us and contributors 👍 |
Description
Fixes an off-by-one in
shufflethat intermittently fails the build while prerendering partner pages. The failure depends on when the build runs, not on the code being built.The failing test and the fix are separate commits, so CI shows both states:
Testsjobd691357f1, test only83eea28d4, with the fixHow it happens
generatePartnersusesMath.floor(Date.now() / 300000)as the seed, so the clock alone picks the shuffle, in 5-minute windows.shufflecomputeshash[hashIndex] / 255, which is exactly1when the byte is 255. That makesj = i + 1.iis the last index, on the first pass of the loop,jis one past the end.undefinedis swapped in and the array grows from 20 to 21 entries.i + 1is still a valid index, so a 255 byte there can't produceundefined. Of the 200,000 seeds tested, 13,371 hit 255 only on a later pass, and none producedundefined.new Set()keeps theundefined, and it's still there when the page reads partners. The error depends on the page's code path:/enpartner.idCannot read properties of undefined (reading 'id')/en/downloadCannot read properties of undefined (reading 'categories')Validation
CI history
/en/downloadwith'categories'at 20:00:26 UTC, and Ubuntu failed on/enwith'id'at 20:01:26. Both are in the 20:00 window. The'categories'failure isn't listed in the issue.Both failing commits have the same 20-entry weighted partner list as
main.Tests with mocked time
partners.test.mjspinsDate.nowto the times of the real failures and to the edges of their windows. Without the fix, the results flip at the millisecond:Of the 22 new tests, 15 fail without the fix and all pass with it. They use a 20-entry fixture because which hash byte gets read depends on the size of the list, so the timestamps stay meaningful if the real partner list changes.
array.test.mjsalso checks a range of seeds at several list lengths, without relying on hand-picked seeds.Seed analysis
Using the real
shuffleand the real partner list, over 200,000 seeds:undefinedwith 0 mismatches.undefinedfalls inside/en's limit of 12 partners for all 779 seeds, so/enwould crash in every one of them.Real component at a mocked time
Calling the real
PartnersListwith the props used by each page reproduces the CI errors exactly, and only inside the window:/en/en/download/enreading 'id'/en/downloadreading 'categories'/en/en/downloadReal static export builds
These ran
node --run deploylocally withNEXT_PUBLIC_STATIC_EXPORT_LOCALE=false, preloading a script throughNODE_OPTIONSthat overrides onlyDate.now, so that it stays inside a chosen window:/en/download/alphawith'categories'Neither of the last two runs is a complete export. The clean run didn't finish copying the export output, and the patched run was stopped before prerendering finished and before it reached
/en, so/en's result with the fix comes from the component check above. CI on this PR completed the export with the fix on both Ubuntu and Windows (2,332 of 2,332 pages), though on the real clock and outside a bad window as far as the logs show.Not checked
supportersData.mjsalso callsshuffle, so the fix covers it too, but that caller wasn't tested with real data.Related Issues
Fixes #9321
Check List
pnpm formatto ensure the code follows the style guide.pnpm testto check if all tests are passing.pnpm buildto check if the website builds without errors.Commits passed the pre-commit Prettier, ESLint and type checks. With the fix, the
apps/siteunit tests pass (229 of 229). The remaining items are left for the author to confirm.🤖 Generated with Claude Code