Skip to content

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 into
cheaper-inference:release/v3.8.52from
woodsonl:fix/release-v3.8.52-basereds
Oct 6, 2026
Merged

diegosouzapw merged 4 commits into
cheaper-inference:release/v3.8.52from
woodsonl:fix/release-v3.8.52-basereds

Conversation

@woodsonl

@woodsonl woodsonl commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

⚠️ base-red inherited: #15306

Dedicated base-reds PR per AGENTS.md — no feature changes. Three CI defects red PRs against release/v3.8.52 regardless 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 mode

Since #15281 (69cb18d), scripts/check/check-cycles.mjs exits 1 on ANY cycle unless --ratchet is passed; #15281 moved ci.yml to check:cycles:ratchet (ceiling quality-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 cycles from the gates array into ratchet_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 parent

PR_BASE_SHA comes from github.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), so git merge-base <stale-sha> HEAD made the base's newer commits part of the diff. #15346 and #15322 were both blamed for FeatureFlagCard.tsx 1 → 2, a violation #12740 introduced on the base.

Fix: resolveMergeBase() 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 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.ts build 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 port

tests/integration/batch-e2e-rate-limit.test.ts picks SERVER_PORT via getFreePort() (bind 0 → close) at module load; run-next.mjs binds only after prepareWithHeal, 15-18s later. An occupant in that window killed the boot with listen 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 — bounded listenWithRetry (6 tries, 5s apart) that retries EADDRINUSE and rejects with the original error once exhausted; wired into run-next.mjs with 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:unit files touched: 2 added/extended; no production routing/DB code changed.
  • Changelog fragment to follow once this PR has a number.

…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.
@woodsonl
woodsonl requested a review from diegosouzapw as a code owner October 5, 2026 21:42
@diegosouzapw
diegosouzapw merged commit b8c50e1 into cheaper-inference:release/v3.8.52 Oct 6, 2026
42 of 51 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants