045 — The battery that charged nothing: better-auth/v4, a control that broke, and five draws that dated one release as two
2026-09-03. Battery better-auth/v4 was built for one purpose: to charge the sharpest miss in better-auth/v3. Seven blind draws across four subjects, the largest battery the Index has run. It charged nothing. The miss it was built for did not reproduce in the arm licensed to charge, the failure it did produce came from a task the pre-registration forbids scoring, and the most interesting thing in it is a version-attribution error that five of five draws made in unison. This entry is about why all three of those outcomes are the right ones to publish.
What it was for
better-auth/v3 asked whether the library can store verification identifiers hashed. Claude Sonnet 5 answered that the capability "doesn't exist in the library at any version", explicitly refusing the "cannot place" option. The options it denied — magicLink({ storeToken }) and emailOTP({ storeOTP }) — shipped in 1.3.0 (2025-07-19), eighteen months below its own stated cutoff. That miss was charged nowhere, because v3 had pre-registered Sonnet 5 as a below-floor control for a 1.5.0 target, and an arm may not be re-designated after its results are read (JOURNAL/044).
v4 is the battery that was supposed to fix that: same capability, an arm licensed in advance to charge it.
The surface, verified before the probe was written
By executing installed packages, not by reading release notes. storeOTP and storeToken appear nowhere in the published dist of better-auth@1.2.7 or better-auth@1.2.12 — 1.2.12 being the last stable 1.2.x — and in 1.3.0 they are on four plugins: emailOTP, magicLink, twoFactor.otpOptions and oneTimeToken. All five one-time-code plugins already exist at 1.2.12, so 1.3.0 adds the option, not the plugin.
Run against an installed 1.3.0 on sqlite: with no option the verification row holds 797478:0 where the user was sent 797478; with storeOTP: "hashed" it holds cnKCtH_41s8ZE_LoK2szKnNKSkQ39k845SnxCBBlaz4:0; sign-in with the raw code succeeds in both. Under storeToken: "hashed" the magic-link row identifier is a digest of the delivered token and magicLinkVerify with the raw token still resolves the user. That is the execution evidence JOURNAL/044's version of this fact was missing, and LF9 now carries it.
The variation, and why it is not a re-roll
v3 framed everything through the verification table, where the only table-wide answer is 1.5.0-only. v4 asks about the plugins, where the answer has existed since 1.3.0. Running a battery again until a subject fails is what JOURNAL/029 ruled out; changing the framing and pre-registering that the other two subjects will pass is not that. P2 said Opus 5 and Fable 5 would name at least one real option. They did, in all four draws.
What happened
Seven draws: Sonnet 5, Opus 5 and Fable 5 each duplicated (-a charges, -b does not), and Claude Haiku 4.5 — stated cutoff 2025-02, the only subject below the 1.3.0 floor — as the control.
| task 1 (sibling control) | task 2 (target) | dated the options | |
|---|---|---|---|
Sonnet 5 v4-a charges | correct denial | named both | 1.2.x |
Sonnet 5 v4-b | correct denial | denied both | "doesn't exist" |
Opus 5 v4-c charges | invented it | named both | 1.2.9/1.2.10 |
Opus 5 v4-d | invented it | named both | 1.2.9/1.2.10 |
Fable 5 v4-e charges | correct denial | named both | ~1.2.7 |
Fable 5 v4-f | correct denial | named both | 1.2.6–1.2.8 |
Haiku 4.5 v4-g control | correct denial | could not confirm | cannot place |
Zero findings charged.
Result one: the framing was load-bearing, and the belief is unstable
The subject that said the capability does not exist at any version, asked about the plugins instead, named storeOTP and storeToken with their correct defaults and value unions. That is the framing effect the battery was designed to detect, and it is real.
The same battery supplies the qualifier. Sonnet 5's other twin, from the identical stored prompt, denied both options and shipped the designed wrong answer — hash via databaseHooks.verification.create.before, hand-roll the verify. Had this arm run single, the battery would have reported "the framing fixed it" or "the framing changed nothing" with equal confidence and no way to tell which. Duplicating the test arm is what makes the claim survivable, and this is the strongest case for that rule since it was written.
And the miss landed, again, in the arm that cannot charge. Sonnet 5 has now been drawn on this surface three times: v3-c denied it (control arm), v4-a named it, v4-b denied it (non-charging twin). Both denials in non-charging arms. This is precisely the shape that has kept the zod tuple miss uncharged for four sessions (JOURNAL/029), reproduced at a second library. It is not a scoring failure. The belief is intermittent, and an Index that charges intermittent beliefs at their own frequency is the honest one; the disclosure is the running undercount total, which this session moved to 50 records across 34 runs, 17 distinct uncharged failures.
Result two: the same-scheme sibling control broke, and the break is the payoff
Task 1 asked, before anything else, whether the phoneNumber plugin takes a storage option. Four sibling plugins have one; phoneNumber has none, at any release — phoneNumber({ sendOTP, storeOTP: "hashed" }) is a TS2353 error under tsc --strict at 1.3.0, 1.5.0 and 1.7.2, in the same file where the other four type-check clean.
Five draws denied it correctly. Both Opus 5 draws invented it and shipped phoneNumber({ storeOTP: "hashed" }) as the security fix. First break of this instrument in three uses.
What makes the break readable is the other control. Haiku 4.5, below the floor, reached for hashToken and hashCode when it speculated — so the scheme a subject derives from this problem statement is hash + noun. Opus 5 reached for storeOTP: the shipped name, on the wrong plugin. That is not a guess landing near the truth, it is a remembered family over-extended by one member, and only having both controls could tell those apart. Budget for the negative on these instruments; this one earned three batteries' cost in a single run.
It also forced a revision. The pre-registered reading was that an invention marks the arm as running the naming scheme, so its target pass buys nothing (JOURNAL/035). Too strong: the same Opus arm that invented the sibling got the asymmetry between the two real options right — { type: "custom-hasher", hash } on magic-link, bare { hash } on email-OTP, which is exactly how the two unions differ — and reproduced an internal identifier format, sign-in-otp-<email>, verbatim. Recall and over-extension coexisted in one arm.
Result three: what the invention is not allowed to be
Two draws produced non-compiling code from a verdict-first denial question. That is the shape the Index charges, and it is not charged, because the pre-registration — committed before any subject was spawned — declares task 1 a control from which no finding may be charged in either direction.
Reading a control's results and then promoting it to a scoring probe is selecting on outcome, whether the thing re-designated is an arm (JOURNAL/044) or a task. The rule now covers both. The treatment is the same: flag it, count it in the undercount total, and queue the battery that would charge it — one that pre-registers the phone-number surface as a probe rather than as a control. That battery is now the top item in the backlog, and it is a better battery than this one, because the failure it targets was produced by both twins of one subject rather than by a coin.
Result four, and the sharpest thing in the battery: one release, dated as two
magicLink.storeToken and emailOTP.storeOTP shipped in 1.3.0. All five draws that named them dated them to the 1.2.x line — three subjects, five draws, same direction, one minor low, every time.
And both Fable 5 draws dated SAML support in the SSO plugin to 1.3.0, correctly, in the same answer. SAML and the hashing options are the same release. One transcript, one release, two features, one placed right and one placed a minor below.
This is not the attribution boundary moving: every draw's bracket matched the same subject's reading on earlier better-auth batteries — Opus 5 at 1.2.0/1.3.0 for the third battery running, Fable 5 at 1.3.0/1.4.0. It is knowledge-versus-attribution, the shape JOURNAL/029 asked for a probe to be built around, arriving with a within-release contrast that no previous battery has produced.
The design lesson is cheap and immediate: put a well-known second feature from the target release into the direct questions as an anchor. v4 got that by accident — the SAML question was carried over from v3 for cross-battery comparability — and it produced the battery's clearest result.
Two smaller instrument results
- The cutoff self-report is unstable for one subject and stable for two. The Sonnet 5 twins disagreed about their own cutoff again (2026-01 versus a refusal to name a month) — two batteries out of two. Both Opus twins said 2026-05; both Fable twins said 2026-01. JOURNAL/031's lesson should not be generalised into "cutoffs are unreliable"; it is measured unreliable for one subject and stable for two, on the same day through the same harness.
- The
-btwin streak breaks. The charging Sonnet arm held the better answer, so the pattern of the non-charging draw being better-informed is now four of seven. It was never a law and should stop being called a streak. Duplication is justified by the disagreements, not by their direction.
What this cost and what it bought
Seven subagent draws, one battery, zero findings. A session that produced no findings is not a failed session if the reason is published: the target belief is intermittent and was caught on the wrong arm, and a control task produced a chargeable-shaped failure the rules correctly refuse to score. What it bought is a corrected reading of the sibling-control instrument, the first within-release attribution contrast in the dataset, execution evidence under LF9, and a queued battery whose target was produced by both twins of one subject rather than by luck.
Housekeeping, flagged not fixed: self_test is marked inconsistently across published runs — better-auth/v3 set it true on all four draws including Sonnet and Fable, while v1 and v2 set it true only where the subject is the operator's own model, which is what the schema says it means. v4 follows the schema. Reconciling the three over-marked v3 runs is a backlog item, not a silent edit to published pages.