Repository navigation
fix(release): drain three v3.8.52 base-reds — cycles fast-gate, stale PR base sha, integration port race - #15590
Merged
diegosouzapw merged 4 commits intoOct 6, 2026
Conversation
…rence#15306) Since cheaper-inference#15281 (69cb18d) check-cycles.mjs exits 1 on ANY cycle unless --ratchet is passed, and ci.yml moved to check:cycles:ratchet (ceiling: quality-baseline.json metrics.cycles = 14). quality.yml's fast-gates loop kept the plain check, so every PR against release/v3.8.52 failed Fast Quality Gates / Gate / Quality with the same 14 SCCs (e.g. cheaper-inference#15346, cheaper-inference#15499, cheaper-inference#15516; runs 37009496871, 37337133696). Move cycles into the ratchet_gates array so it runs with --ratchet, identical to ci.yml. Validated locally: npm run check:cycles exits 1 (cycles=14), npm run check:cycles -- --ratchet exits 0.
cheaper-inference#15306) quality.yml passes PR_BASE_SHA from github.event.pull_request.base.sha, which stays at the commit the PR was OPENED against. The job checks out GitHub's merge ref (refs/pull/N/merge — HEAD is a merge commit whose first parent is the LIVE base tip), so merge-base(stale-sha, HEAD) made the base's own newer commits part of the diff and blamed them on the PR: cheaper-inference#15346 and cheaper-inference#15322 were both flagged for a FeatureFlagCard.tsx 1 -> 2 regression that cheaper-inference#12740 introduced on the base. resolveMergeBase() now returns HEAD^1 when HEAD is a merge commit and the base sha is an ancestor of it — exactly the PR's new code, matching GitHub's Files changed view. Linear (local) HEADs keep merge-base semantics; diverged branches fall through to merge-base as before. Both new-code consumers (complexity-ratchets, dead-code) route through this helper. Unit tests build a real merge-ref-shaped repo fixture; the merge-commit test fails on the old implementation and passes now.
…er-inference#15306) tests/integration/batch-e2e-rate-limit.test.ts picks SERVER_PORT with a bind(0)-release at module load, but run-next.mjs only binds the port after next prepare() (~15-18s). Anything claiming the port inside that window killed the boot with EADDRINUSE (exit 1) and red the integration job (CI run 111855214488) even though the conflict is transient — the suite runs with --test-concurrency=1, so the occupant is a previous file's server still tearing down. Extract a bounded listenWithRetry (6 tries, 5s apart): retry while the error is EADDRINUSE, reject with the original error once attempts are exhausted so a genuine conflict still fails loudly. Post-listen server errors stay fatal via the same handler, now attached after the bind. Unit test pins the contract (retry-to-success, exhaustion, free port); live repro: a 25s port squatter no longer kills the boot — the server logs 4 retries and binds after release.
diegosouzapw
merged commit Oct 6, 2026
b8c50e1
into
cheaper-inference:release/v3.8.52
42 of 51 checks passed
Merged
8 tasks done
3 of 4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Dedicated base-reds PR per AGENTS.md — no feature changes. Three CI defects red PRs against
release/v3.8.52regardless of their code (evidence: #15346, CI runs 37009496871 / 37337133696 / 37337133864, job 111855214488). None are listed in #15306 yet.1. Cycles gate —
fix(ci): run the fast-gates cycles gate in ratchet modeSince #15281 (69cb18d),
scripts/check/check-cycles.mjsexits 1 on ANY cycle unless--ratchetis passed; #15281 moved ci.yml tocheck:cycles:ratchet(ceilingquality-baseline.json→metrics.cycles = 14) but left quality.yml's fast-gates loop on the plain check. Every PR failed Fast Quality Gates → Gate / Quality with the same 14 SCCs.Fix: move
cyclesfrom thegatesarray intoratchet_gates(the loop appends--ratchet), identical semantics to ci.yml.Validation:
npm run check:cycles→ exit 1 (cycles=14);npm run check:cycles -- --ratchet(the exact command the loop now runs) → exit 0. Workflow-only change, checked with the gate script locally as allowed by the base-red protocol.2. Complexity ratchet —
fix(quality): diff new-code gates against the merge ref's first parentPR_BASE_SHAcomes fromgithub.event.pull_request.base.sha, which stays at the commit the PR was opened against. The job tests a merge into the live base tip (HEAD is GitHub's merge commit, first parent = live base tip), sogit merge-base <stale-sha> HEADmade the base's newer commits part of the diff. #15346 and #15322 were both blamed forFeatureFlagCard.tsx1 → 2, a violation #12740 introduced on the base.Fix:
resolveMergeBase()returnsHEAD^1when HEAD is a merge commit and the base sha is an ancestor of it (exactly the PR's new code, matching GitHub's Files changed view). Linear/local HEADs and diverged branches keep merge-base semantics. Both consumers (complexity-ratchets,dead-code) route through this helper.Validation (TDD): new unit tests in
tests/unit/build/new-code-mode.test.tsbuild a real merge-ref-shaped git repo; the merge-commit test fails on the old implementation and passes with the fix (11/11 green).3. Integration port race —
fix(dev): retry the run-next listen on a transiently busy porttests/integration/batch-e2e-rate-limit.test.tspicksSERVER_PORTviagetFreePort()(bind 0 → close) at module load;run-next.mjsbinds only afterprepareWithHeal, 15-18s later. An occupant in that window killed the boot withlisten EADDRINUSE→ exit 1 (job 111855214488). The suite runs--test-concurrency=1, so the conflicts are transient (previous file's teardown), not a long-lived sibling.Fix: new
scripts/dev/listen-with-retry.mjs— boundedlistenWithRetry(6 tries, 5s apart) that retriesEADDRINUSEand rejects with the original error once exhausted; wired intorun-next.mjswith the fatal post-listen error handler attached after a successful bind.Validation (TDD + live repro):
tests/unit/run-next-listen-retry.test.ts(3 tests: retry-to-success, exhaustion, free port) — red before the helper existed, green after; all 26 run-next-related unit tests green. Live repro of the defect: a 25s port squatter no longer kills the boot — the server logged 4 retries and bound after release (old code exits on the first attempt).Notes
test:unitfiles touched: 2 added/extended; no production routing/DB code changed.