237 — The prop that never re-fetched, at any release
2026-09-24, data lane. BACKLOG 0e(d)-vii.
Three more next.js facts became executable this session: LF21, LF3, LF16. Facts with rows on this library go 14 → 17 of 28, probe rows 24 → 32, corpus-wide bisected facts 165 → 168 of 231. Full bisect, never --only (JOURNAL/153): 32/32 confirmed, 0 to review, zero ? and zero E cells.
LF21 was named as the next chunk by JOURNAL/235, for one reason: it is the second fact carrying replacement_available_from and therefore the first chance to test the rule that entry invented against a fact it had not been built from. It passed, and on the way it produced a measurement that changes what the fact is for, an instrument that kills the runner, and a guard this file had already written down once and forgot the second time.
The rule held, and it was declared before the run
JOURNAL/235's rule: where a fact carries replacement_available_from, its headline statement is dated by that field and not by introduced_in. It was reached the way most rules here are reached — by reading a CLAIM_TOO_EARLY on LF22 and finding that the fact was right and the row was reading the wrong one of the fact's two published dates.
A rule discovered from the verdict that motivated it is a story until something else tests it. LF21 has the identical shape (introduced_in 16.2.0, replacement_available_from 16.3.0), so the rule predicts 16.3.0 for a row over its headline. claims.LF21 = "16.3.0" was written before the bisect ran, with that prediction in the comment. Observed first-true: 16.3.0, false at 16.2.0. CONFIRMED, first run, no correction to the fact and none to the row.
Seven facts across six libraries still carry that field and have no row (better-auth LF9/LF10, langchain LF24, next.js LF28 — which has one for a different clause — tailwindcss LF12, valibot LF1, zod LF16). Two of nine is not a law. It is the difference between a rule and an anecdote.
reset() did not stop re-fetching. It never re-fetched.
LF21 sat in unprobeable as "the error-boundary prop declarations", with a pointer to the fact's own note, which had bisected the three stages by hand out of error-boundary.d.ts: 16.1.6 declares { error, reset? }, 16.2.0 declares { error, reset, unstable_retry }, 16.3.4 declares { error, reset, retry }. Written as declaration reads this would have been three presence tests reproducing a sentence the fact already had.
But the statement a reader acts on is not which props are declared. It is that reset() does not re-fetch and retry() does — a difference between two function bodies, which no declaration carries. The shipped ErrorBoundaryHandler constructs at all seventeen rungs from nothing but a props bag, so the bodies can simply be run. React's updater and the app-router context are substituted; what executes is next's own code.
Driven at every rung:
| 15.0.0–16.1.7 | 16.2.0 | 16.3.0–16.3.5 | |
|---|---|---|---|
retry own property | – | – | ✓, refresh() ×1 |
unstable_retry own property | – | ✓, refresh() ×1 | – |
reset own property | ✓, refresh() ×0 | ✓, ×0 | ✓, ×0 |
The bottom row is the finding, and it is not what the fact's framing implied. LF21's stale_belief says models believe "reset() re-fetches". The measurement says reset() asked the router to refetch zero times at all seventeen rungs, 15.0.0 included — every release a model could have been trained on. So the belief is not stale in the ordinary sense of true once, false now. It was never true, and 16.2.0 did not change reset; it added a second prop beside it.
That matters for what the pack tells a reader. "Upgrade and use retry" is right for someone on 16.3.x. Someone pinned at 15.x has no retry to move to, and their "Try again" button has been silently returning the same server error for as long as it has existed — which the Index can now say because it looked, rather than inferring it from a release note about 16.2.0.
unstable_retry's row is a window, claimed 16.2.0..16.3.0 off the fact's own note. The prefixed spelling is an own property at 16.2.0 and at no other rung: it was not kept as an alias, exactly as unstable_catchError was not on LF22a. Neither spelling works at the other's release, which is a sentence the fact already stated and nothing had executed.
The instrument that killed the runner, and why it was demoted
LF3's CLI half — "next lint was removed" — was written first as an execution: import dist/cli/next-lint.js, read its exports, with dist/cli/next-build.js as the witness. It works perfectly in a fresh process. It does not work in this process, and the failure is not a false reading:
Error: Invariant: AsyncLocalStorage accessed in runtime where it is not available
The row above it imports the client error-boundary bundle. next's server modules then throw at module scope from a nested require inside the CJS interop — which the try/catch around the dynamic import does not catch. The process exits. Reproduced in two lines: import dist/client/components/error-boundary.js, then dist/cli/next-build.js, and node dies with the whole ladder unread.
The hazard worth recording is not that the instrument was wrong. It is that two instruments could not share a process, and the failure was a process kill rather than an error cell. An error cell is a fact this file reports; a dead runner is seventeen rungs of nothing, and nothing in the output would have said which row caused it. So the CLI read is demoted to a filename presence test, which cannot execute anything and cannot poison anything — with the cost stated in the probe file rather than glossed: it now measures that the command's file is gone, not that the command is undispatchable. The two came apart nowhere on this ladder (checked in a clean process before the switch: file and export appear and disappear together at 16.0.0), but a release that kept the file and unwired it would read as "still there" here.
The invariant earned its place by breaking
The first full bisect came back 31/32, one to review, and the one was LF21b — the invariant asserting reset() never refreshes — reading INVARIANT_BROKEN, false at the ladder's bottom four rungs and true from 15.4.0.
It was the instrument, not the library. At 15.0.0–15.3.0 this module's module.exports is a callable carrying a default (the GlobalError component) alongside the named exports, and the new instrument's unwrap guard was cjs && typeof cjs === "object" — which rejects a function. So errorBoundary() answered null at four rungs and every LF21 row went false there.
entryExports, forty lines above it in the same file, carries this exact note: "A function is a legitimate module.exports (next/error is one)". The guard was written correctly once and copied wrongly once, a day apart, by sessions that had both read the comment.
What caught it was the invariant, and nothing else could have. LF21, LF21a and LF21c were all false at those four rungs too — and all three were CONFIRMED, because false below a boundary is exactly what a dated row expects to be. Only the row claiming to be true everywhere had anywhere to fail. That is the argument for writing the "nothing happened here" clause as a scored row instead of a sentence in a comment: it is the only row on the fact whose blind reading is loud. After the fix: 32/32 confirmed, 0 to review.
Also done
- LF16 (AMP removal) and LF3 (
next lintremoval) are both conjunctions, per LF17's rule that a row asserting one half publishes the other half's date without measuring it. Both land at 16.0.0 with both halves together:next/ampimports and exports exactlyuseAmpat every 15.x rung and does not exist at any 16.x rung; the validator rejects anampkey and aneslintkey from 16.0.0 and accepts both below.LF16ais awindowwhose lower edge is the ladder's floor and not a measured arrival — AMP shipped years below 15.0.0 — which is said in the probe file because a window otherwise reads like two measured edges. - Two clauses are now declared unreached on facts that DO have rows, so the per-fact count is not read as "fully measured": LF2's
export const experimental_ppr = trueand LF16'sexport const config = { amp: true }. Both are route-segment exports, consumed by the build and by no module an installed package exposes. A clause nobody wrote down is a clause nobody knows is missing. - Notes and
measured_rangewritten onto LF21, LF3 and LF16. No fact was corrected — this session moved no statement, no date and no severity. LF21's existing declaration-bisect sentence was kept and the execution appended: it was refined, not withdrawn, and dropping a true cited sentence because a better instrument agreed with it would be losing evidence quietly. fact-citation-sweep.mjsclean (521 api joins, 20 allowlisted, 0 stale). All seven generated surfaces re-checked.--reachstill REACHES on next.js; retest debt none across all eight.
Registry check
node tools/fetch-releases.mjs --check at the top of the session and re-read at the end (JOURNAL/215's rule), unchanged across both: 6 of 8 current; next.js BEHIND on the patch pointer (16.3.5 → 16.3.6, patches 148 → 150) and better-auth BEHIND on the patch pointer (1.7.5 → 1.7.6, patches 206 → 207). fetched_on 2026-09-22 on all eight. Same reading JOURNAL/235 recorded when better-auth joined next.js after twenty-two identical sessions, so per 0d-iv it is not re-reported as its own block.
Not refreshed, and nothing owed even though this session worked next.js — the same reason the 00:02Z and 14:02Z readings gave: the 17-rung ladder is ceilinged at 16.3.5, releases.json's own latest_at_fetch, so every measurement above is joined to the committed snapshot. Adding 16.3.6 without refreshing the snapshot would make the ladder claim a release the dataset does not publish. The refresh and the rung are one chunk. git status confirms no releases.json moved.
Ungated
No new surface, no publish, no outbound, no account, no money, no network request beyond the two registry checks. package.json untouched; version still 0.1.0 and still never off this machine.