@samitouri / QOS-React-1 / commits / 373d35eee5

[ci] Pin build shard weights per run to keep worker assignments identical (#37373)

The build workers can restore different weights if they don't restore the cache at the exact same time. The more time difference, the more likely they restore different weights which could lead to some bundles not being built at all (e.g. https://github.com/react/react/actions/runs/32822851267). A new job now restores the latest entry once per run and republishes it as a per-run artifact. The new job sits adds no wall time because it runs in parallel with `runtime_compiler_node_modules_cache`, which already gates the build workers and takes about 30 seconds on a cache hit, while the resolve job does strictly less work (no checkout, no Node setup, a 5KB cache entry instead of the node_modules restore), so it finishes first and the build workers start at the same time as before. Co-authored-by: Claude Code (kimi-k3[1m]) <noreply@anthropic.com>

Sebastian "Sebbie" Silbermann committed Aug 26, 2026 at 07:58 UTC 373d35eee53e3bcb0be4b31eea70f3b67ce5e4c1
1 file changed +48 -18
.github/workflows/runtime_build_and_test.yml
+48 -18
@@ -82,6 +82,38 @@ jobs:
82 - run: yarn --cwd compiler install --frozen-lockfile
83 if: steps.node_modules.outputs.cache-hit != 'true'
84
85 + # ----- BUILD WEIGHTS -----
86 + # Pins one weights snapshot for the whole run. A cache restore with
87 + # restore-keys returns the newest entry at call time, so 50 workers
88 + # restoring individually can disagree when another run saves a new entry
89 + # mid-fan-out, which scrambles the shard assignment and drops bundles from
90 + # the build.
91 + resolve_build_weights:
92 + name: Resolve build shard weights
93 + runs-on: ubuntu-latest
94 + outputs:
95 + available: ${{ steps.available.outputs.available }}
96 + steps:
97 + - name: Restore latest build shard weights
98 + id: weights
99 + uses: actions/cache/restore@v4
100 + with:
101 + # Must match the save step's path in process_artifacts_combined
102 + # exactly: the cache version is a hash of the path, so a different
103 + # path never matches the key.
104 + path: build-weights.json
105 + key: build-weights-v1-${{ github.run_id }}
106 + restore-keys: build-weights-v1-
107 + - id: available
108 + run: echo "available=${{ steps.weights.outputs.cache-matched-key != '' }}" >> "$GITHUB_OUTPUT"
109 + - name: Publish build shard weights for this run
110 + if: steps.available.outputs.available == 'true'
111 + uses: actions/upload-artifact@v4
112 + with:
113 + name: build-weights
114 + path: build-weights.json
115 + if-no-files-found: error
116 +
117 # ----- FLOW -----
118 discover_flow_inline_configs:
119 name: Discover flow inline configs
@@ -269,7 +301,7 @@ jobs:
301 # ----- BUILD -----
302 build_and_lint:
303 name: yarn build and lint
272 - needs: [runtime_compiler_node_modules_cache]
304 + needs: [runtime_compiler_node_modules_cache, resolve_build_weights]
305 runs-on: ubuntu-latest
306 strategy:
307 fail-fast: false
@@ -307,15 +339,14 @@ jobs:
339 if: steps.node_modules.outputs.cache-hit != 'true'
340 - run: yarn --cwd compiler install --frozen-lockfile
341 if: steps.node_modules.outputs.cache-hit != 'true'
310 - - name: Restore build shard weights
311 - uses: actions/cache/restore@v4
342 + # Pinned once per run by resolve_build_weights so every worker computes
343 + # the same shard assignment. Absent on a cold start; the build then
344 + # falls back to round-robin sharding.
345 + - name: Download build shard weights
346 + if: needs.resolve_build_weights.outputs.available == 'true'
347 + uses: actions/download-artifact@v4
348 with:
313 - # Written by process_artifacts_combined on every push. The
314 - # restore-keys prefix picks up the most recent entry. On a miss the
315 - # build falls back to round-robin sharding.
316 - path: build-weights.json
317 - key: build-weights-v1-${{ github.run_id }}
318 - restore-keys: build-weights-v1-
349 + name: build-weights
350 # setup-java's exports (e.g. JAVA_HOME) only apply to steps after this wait.
351 - name: Wait for Java setup
352 wait: setup_java
@@ -469,7 +500,7 @@ jobs:
500
501 process_artifacts_combined:
502 name: Process artifacts combined
472 - needs: [build_and_lint, runtime_node_modules_cache]
503 + needs: [build_and_lint, runtime_node_modules_cache, resolve_build_weights]
504 permissions:
505 # https://github.com/actions/attest-build-provenance
506 id-token: write
@@ -505,15 +536,14 @@ jobs:
536 - name: Wait for archived build
537 wait: download_build
538 # Only used to log weight variance; the new measurement is what gets
508 - # saved, so removed bundles drop out instead of accumulating.
509 - - name: Restore previous build shard weights
510 - uses: actions/cache/restore@v4
539 + # saved, so removed bundles drop out instead of accumulating. Downloads
540 + # the run's pinned snapshot so the diff compares against the weights
541 + # that actually drove this run's assignment.
542 + - name: Download previous build shard weights
543 + if: needs.resolve_build_weights.outputs.available == 'true'
544 + uses: actions/download-artifact@v4
545 with:
512 - # Must match the save step's path exactly: the cache version is a
513 - # hash of the path, so a different path never matches the key.
514 - path: build-weights.json
515 - key: build-weights-v1-${{ github.run_id }}
516 - restore-keys: build-weights-v1-
546 + name: build-weights
547 - name: Update build shard weights
548 run: node scripts/ci/merge-build-weights.js
549 - name: Save build shard weights