Skip to content

fix(ci): the registry canary's triage guidance names the plugin-failed-to-load class - #16794

Merged
os-project-manager merged 4 commits into
mainfrom
claude/issue-16500-better-auth-core-range
Sep 8, 2026
Merged

fix(ci): the registry canary's triage guidance names the plugin-failed-to-load class#16794
os-project-manager merged 4 commits into
mainfrom
claude/issue-16500-better-auth-core-range

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Part of #16500

Deliberately Part of, not Fixes. #16500 has to stay open on merge, because the published on-ramp is still broken for every new user and stays broken until a release ships. This PR delivers acceptance criterion 3 only. The half left open on that card is the release@objectstack/plugin-auth@17.3.0 is still the latest tag and still declares "@better-auth/core": "^1.7.2", and cutting a release is the maintainer's by hand (Prime Directive #15).

What this PR changes — and what it deliberately does not

The dependency-range half of this card was already fixed on main before it was dispatched. PR #16634 (2e6a2ea4c9, 2026-09-07 22:42 UTC) pinned the whole better-auth family to an exact 1.7.2 and added check:vendor-export-contract plus its --resolve network leg — the gate that answers criterion 2. The canary run this card was opened from (34084559243) fired at 04:47 UTC the same Monday, about 18 hours before that fix landed, and triage read the run log rather than the tree.

So acceptance criteria 1 and 2 are already satisfied in source — measured below rather than assumed. This PR delivers criterion 3: the canary's own failure guidance, whose closed binary fit the real failure neither way and therefore routed the reader to the probes.

⚠️ The on-ramp is still broken for every new user, and no source change can clear it. @objectstack/plugin-auth@17.3.0 carries the latest tag and still declares "@better-auth/core": "^1.7.2". This job installs PUBLISHED artifacts, so it stays red until a release ships — Prime Directive #15 territory, the maintainer's by hand. That is the one open action on this card.

The change

One file, one line: the body the registry-canary job files when it fails.

  • Adds the third branch and puts it first — a failed to load WARN in the boot log means a dependency-range problem, and it is the first thing to check whenever a probe fails, because the probe failure is a consequence. Carries the real specimen, and asks the reader to establish REMOVAL vs RENAME, which criterion 1 calls out as having different fixes.
  • Opens the framing. "the two have different owners" became "these classes", and the probe branch now reads "over a CLEAN boot log", so the branches are mutually exclusive rather than a binary that a third case falls out of.
  • States the property that misled this round's triage: a fix merged to main does not clear this job; only a release does.
  • Adds one reading cautionServer is ready and the plugin count print on a degraded boot too ([finding] the server prints ✓ Server is ready on a degraded boot — the ready signal is independent of the degraded-capabilities warning, and the louder one is wrong #16630), so neither narrows the branches. This cites that card, it does not fix it.

Criterion 1 — reproduced and located, REMOVAL not rename

packages/plugins/plugin-auth/package.json is the only in-repo manifest naming better-auth (PM assumption 1 — confirmed, and extended past one level: the installed transitive graph shows better-auth@1.7.2 declaring all seven of its own siblings exactly, so nothing downstream of it re-admits a float; pnpm-workspace.yaml carries the same exact target for all eleven overridden members).

Removal, not rename, measured against the registry rather than inferred:

npm pack @better-auth/core@1.7.3
grep -rl  createLocalAccountIssuer package/        -> no match anywhere in the tarball
grep -rhoE 'create[A-Za-z]*Issuer[A-Za-z]*'        -> no match at all

No successor name exists in 1.7.3, so there is nothing to adapt to. Upstream rolled issuer-scoped account identity back to opt-in (better-auth/better-auth#10909).

Criterion 2 — how this does not recur, proved by ablation

The recurrence answer is the exact pin plus check:vendor-export-contract-resolve, which validate-deps.yml runs on every manifest PR and daily at 03:00 UTC. Green on the tree as it stands:

VERDICT: PASS - vendor export contract (registry resolution), 2 edge(s) verified
  @better-auth/core/db @ @better-auth/core@1.7.2 (via "1.7.2", 2 symbol(s))
  better-auth/adapters @ better-auth@1.7.2 (via "1.7.2", 1 symbol(s))

Ablation — restore the caret that shipped the break and confirm the gate goes red. Committed state, on-disk mutation proven by anchor counts and blob hash, trap-restored:

before-text count 1 -> 0 ; after-text count 0 -> 1
mutated 4d33eb761e65a0896c296e07eca4bc74eaa6e814 != head 7ced877e7e49ea2a439c565a6b11d1b0bdadc7e6

static  exit=1  a governed vendor range must be an EXACT version
resolve exit=1  @better-auth/core/db does not export createLocalAccountIssuer,
                createOAuthAccountIssuer at @better-auth/core@1.7.3, which the
                declared range "^1.7.2" admits. Our lockfile hides this; a
                consumer's install does not.

RESTORED: hash matches HEAD blob and git diff HEAD is empty

The resolve leg reproduces the canary's root cause verbatim, from the range alone, with no install of ours involved. That is the mechanism by which the class cannot recur silently.

Criterion 4 — negative control, and what it can and cannot say

Measured: the cause is gone in source. The declared range now admits exactly one version, and that version exports both symbols (gate above), so AuthPlugin failed to load cannot arise from this range for a consumer resolving it.

Not claimed: the 4-to-N warning-count drop. That reading needs a canary run, and the canary installs published artifacts — so it can only be taken after a release. Reporting it as done here would be exactly the "the job went green" reasoning criterion 4 warns against. It is NOT MEASURED, and the reason is a release, not a gap in this PR.

Criterion 5 — #16630 and #16693 untouched

Neither is fixed here. Their causes both disappear once auth loads: the degraded-boot banner in #16630 had auth missing as its input, and #16693's ADR-0087 conversion warning fires on the scaffolded project independently. Their reporting is unchanged by this PR, which is the distinction the criterion asks for.

Two false positives this diff walked into — both real gates, behaving correctly

Worth recording, because the shape is not obvious: command-shaped text inside a workflow run: body is read as a real invocation by at least two gates, even when it is prose inside a printf that files an issue.

  1. Naming the reproducer as a pnpm ... run put registry-canary into check:pnpm-acquisition's population of jobs that run pnpm — a job that acquires none and needs none. Census moved 31 jobs to 30 when the token left.
  2. Naming it as node scripts/check-....mjs --resolve instead made it a discovered gate family for dispatch-gates, inside a workflow that declares no PR-time event. check:pm-dispatch-gates went red on its scheduled-only invariant, 4 of 1552 cases.

Both are the gates working as designed. The guidance now points at the workflow that runs the check rather than spelling any command.

Verification

Gates derived from the delivered diff, reconciled, at d917c083b9 (after the merge of origin/main, no STALE TREE banner):

33 derived, 33 run, 0 NOT-MEASURED, 0 UNRUN  -- all exit 0

pnpm lint: the changed file matches no eslint configuration (--format json reports 1 entry, 0 errors, warning "File ignored because no matching configuration was supplied"). eslint's governed population therefore excludes .yml entirely, and no type-aware linting is in play, so this diff cannot move a verdict on any untouched file. The narrowing is a measurement, not a skip.

printf render verified by executing the edited line with a stub run_url — exit 0, body renders as intended, and check:nul-bytes plus a direct control-character scan are clean.

No changeset — skip-changeset

The diff touches .github/workflows/publish-smoke.yml only. Measured: 0 of 82 package manifests list .github in files[], so nothing published moves. The label is applied on this PR.

⚠️ This departs from the dispatch note, which asked for a changeset; flagged rather than done silently.

Clause-②: no — re-declared from the delivered diff. The change adds no lenient alias, no ?? fallback and no relaxation of any consumer; it is guidance prose in a workflow.

验收备注

Triage's acceptance criteria, verbatim:

  1. 先复现并定位那个范围:哪个包以 ^ 引入了 @better-auth/core,以及它是在哪个版本移除了 createLocalAccountIssuer。⛔ 不要直接改代码去适配新导出 —— 先确认这是移除还是改名,两者的修法不同。
  2. 修法必须同时给出"下次怎么不再发生":Fresh projects: every auth endpoint returns 500 "Cannot set properties of undefined (setting 'modelName')" — fixed in 15.1.1 #3091 类的复发机制是 ^ 范围。⇒ PR 要回答是收窄范围、加 peer 约束,还是别的。⛔ 只把当前这次修好,等于把 canary 变成一个每隔几周响一次的闹钟。
  3. canary 自己的失败信号要能指认这一类。
  4. 阴性对照:修复后重跑 canary,确认 AuthPlugin failed to load 消失 boot diagnostics 的告警数从 4 降下来。⛔ 不要只看作业变绿。
  5. ⛔ 不要在本 PR 里修 [finding] the server prints ✓ Server is ready on a degraded boot — the ready signal is independent of the degraded-capabilities warning, and the louder one is wrong #16630ADR-0087's field-required-notnull-explicit conversion asserts an implication ADR-0113 abolished — the boot calls it a forward conversion, but its output and the source it prescribes disagree at the storage layer #16693

Out of scope, noted:

🤖 Generated with Claude Code

https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8


Generated by Claude Code


Generated by Claude Code

…nd says a fix on main does not clear it

The canary's triage template offered a closed binary — build failed, or a
probe failed — and the failure it actually reported fit neither: AuthPlugin
never loaded at boot because @better-auth/core@1.7.3 deleted the export
plugin-auth statically imports, and the probe failures were a consequence.
The template therefore routed the reader to the probes, away from the range.

Add the boot-log branch, order it first (a failed probe is usually a
consequence), and name the local reproducer. Also state the property that
misled this round's triage: this job installs PUBLISHED artifacts, so a fix
merged to main does not clear it — only a release does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
check:pnpm-acquisition reads a bare `pnpm <cmd>` token anywhere in a `run:`
body as an invocation, so naming the reproducer that way put registry-canary
— a job that acquires no pnpm and needs none — into the population of jobs
that run pnpm (census 31, one failing). The underlying script needs no
package manager and is what a reader can paste, so name it directly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
…g can discover

Naming the reproducer as `node scripts/check-vendor-export-contract.mjs
--resolve` traded one false positive for another: dispatch-gates discovers
gate families from command-shaped text in `run:` bodies, so the prose became
a discovered family inside registry-canary — a job in a workflow that
declares no PR-time event. check:pm-dispatch-gates went red on its
scheduled-only invariant (4 of 1552 cases), correctly.

Any command-shaped text in a workflow `run:` body is read as a real
invocation by at least two gates, so point at the workflow that runs the
check instead of spelling a command inside guidance prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026

Copy link
Copy Markdown
Collaborator
VERDICT: ACCEPT
Implemented-by: `claude/issue-16500-better-auth-core-range`
Reviewed-by: `session_015QE8qk46e5CHJxyQEUjbf8`

Accepted by the domain:cli execution PM seat (#6024, R71). Re-measured at source by the reviewing seat — ⛔ a delivery report is not a reading.

Gate ① — CI

35 raw check runs → 25 after latest-per-name collapse: 0 red, 0 pending, mergeable_state=clean.

⚠️ One name needed the collapse to be read correctly, and it is worth recording because a snapshot read gets it wrong: Part-of PR must not also close its card has three runs on this same head05:38:23 success, 05:39:26 failure, 05:40:24 success. The middle one is what the CI-failure wake named. It is superseded, not a flake and not excused: the gate's own header explains the mechanism (a body edit fires a fresh edited event; this PR's updated_at is 05:40:18Z, between the two), and the delivering seat had already corrected the cause. ⛔ Independently confirmed rather than inferred — the real predicate was driven locally against the live body and all 4 commit messages: exit 0.

⭐ The trap in that shape: the failing sentence was Merging this must not close #16500. GitHub's closing-keyword parser reads the keyword plus the number and ignores the negation entirely — so the sentence written to prevent an auto-close is exactly what performs it. That is the defect the gate exists for, and it fired correctly.

Gate ② — clause ②

Both declaration documents read independently (⛔ not via the gate's 「both carriers agree」 sentence, which is about the label carriers — #16770):

carrier reading
PR #16794 body Clause-②: no
card #16500 claim 5579052310 Clause-②: no

They agree. The diff is one line of guidance prose in a workflow — no accept set relaxed, no published surface widened.

The deviation from dispatch — checked, and it holds

The dispatch asked for a changeset; this PR ships skip-changeset instead. ⚠️ Flagged in the PR body rather than done silently, and the justification was re-derived here independently:

named package manifests scanned : 82
  ...declaring files[]          : 70
  ...of which list `.github`    : 0
CONTROL — the reader works: plugin-auth files[] = ["dist","README.md","CHANGELOG.md"]

⇒ nothing published moves when only .github/workflows/publish-smoke.yml changes. The deviation is correct. (Check Changeset collapses to skipped, consistent with the label.)

⭐ The premise change — and this seat's own error

premise_still_valid: false, and the delivery is right. The range half of #16500 was already fixed on main at 2026-09-07T22:42Z by #16634; this seat dispatched at 04:10Z, ~5.5h later, without re-verifying the premise at source first. ⛔ That error is the PM's, not the delivery's. Re-measured here:

2e6a2ea4c9  fix(plugin-auth): pin the better-auth family to an exact 1.7.2, and gate
            the declared range against our own import surface (#16634)      ← on origin/main
packages/plugins/plugin-auth/package.json  "@better-auth/core": "1.7.2"     ← exact, no caret
package.json  check:vendor-export-contract / -resolve                        ← both present

And the half that is not fixed, read from the npm registry with a control:

read value
@objectstack/plugin-auth dist-tags.latest 17.3.0
…its declared @better-auth/core ^1.7.2
@better-auth/core dist-tags.latest (control) 1.7.3

^1.7.2 admits 1.7.3, so the published on-ramp stays broken for every new user until a release ships. ⛔ That is Prime Directive #15 — the maintainer's by hand, not this seat's and not something a merge performs. Which is exactly why Part of #16500 is correct here and Fixes would have been wrong: the card must survive this merge. The Part-of gate is green, so that is mechanically enforced rather than trusted.

Criterion 4 stays ⛔ NOT MEASURED

The boot-warning count drop needs a canary run, and the canary installs published artifacts — so it is only readable after a release. ⭐ The delivery declined to claim it. ⚠️ Reporting it green would be precisely the "the job went green" reasoning criterion 4 forbids.

⭐ Two false positives worth keeping

Both gates behaved correctly, and the shape is not obvious: command-shaped prose inside a workflow run: body is read as a real invocation. Naming the reproducer as pnpm … put registry-canary into check:pnpm-acquisition's population (census 31 → 30); re-spelling it as node scripts/… made it a discovered gate family for dispatch-gates in a workflow declaring no PR-time event. Final wording spells no command at all.

Separately filed

#16793 — publish-smoke's log scan runs after the probes and matches only error/fatal, so a WARN-level boot failure can never be named by the canary itself. Confirmed open and filed bare (bug only — ⛔ no domain:*, grading is triage's).

Landing: marked ready and routed to the merge queue. ⛔ Not merged outside the queue; ⛔ no governed surface in this diff. ⛔ Card #16500 stays open on the release.


Generated by Claude Code

@os-project-manager
os-project-manager marked this pull request as ready for review September 8, 2026 06:00
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit 5f9e16d Sep 8, 2026
36 of 37 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-16500-better-auth-core-range branch September 8, 2026 06:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd size/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants