Repository navigation
bug(plugin-auth): published 17.1.0/17.2.0/17.3.0 float @better-auth/core to 1.7.3, which dropped createLocalAccountIssuer — a fresh objectstack dev --seed-admin never creates the system tables and never seeds #16186
Description
Activity
Second independent reproduction, from a real customer app (os-project-titanwind-ehr, upgrading 17.2.0 → 17.3.0 on 2026-09-06).
What we measured
- Fresh
pnpm installafter bumping@objectstack/*to 17.3.0 resolved@better-auth/coreto 1.7.3 (published 2026-09-06T03:03Z).@objectstack/plugin-auth@17.3.0declares"@better-auth/core": "^1.7.2"and itsdist/index.mjsdoesimport { createLocalAccountIssuer, createOAuthAccountIssuer } from "@better-auth/core/db". grep -c createLocalAccountIssuer dist/db/index.mjs: 1.7.2 → 2, 1.7.3 → 0. So every consumer whose lockfile is not already pinned gets the oclifSyntaxError … does not provide an export named 'createLocalAccountIssuer'at load, exactly as reported above.- Upstream cause: better-auth deliberately rolled the issuer-scoped account identity back to opt-in (fix(auth): make issuer-scoped account identity opt-in better-auth/better-auth#10909, merged 2026-08-24) and shipped the removal in the patch release v1.7.3 — a removed public export (
./dbis a declaredexportssubpath) inside a patch bump, so the^1.7.2range cannot protect against it.
Consumer-side workaround that works (verified: validate/tsc/build green, dev +
NODE_ENV=productionboots, PostgreSQL 16 boot, full login → CRUD → record-action chain):"pnpm": { "overrides": { "better-auth": "1.7.2", "@better-auth/core": "1.7.2", "@better-auth/oauth-provider": "1.7.2", "@better-auth/scim": "1.7.2", "@better-auth/sso": "1.7.2", "@better-auth/drizzle-adapter": "1.7.2", "@better-auth/kysely-adapter": "1.7.2", "@better-auth/memory-adapter": "1.7.2", "@better-auth/mongo-adapter": "1.7.2", "@better-auth/prisma-adapter": "1.7.2", "@better-auth/telemetry": "1.7.2" } }
Suggested fix on the platform side (either): pin the better-auth family to an exact version in
plugin-auth'sdependencies(the family rides in lockstep, a caret range on a one-month-old API is what let this through), or move to the 1.7.3 opt-in spelling and raise the floor to>=1.7.3. Until one of those ships, every new install of 17.1.0–17.3.0 is broken out of the box, which is the shape #3085 already closed once for 1.7.0-rc.1.- Fresh
One seam this card's failure exposes that it does not itself cover: the server prints
✓ Server is readyon the broken bootobjectui
domain:uiPM seat (session_01YBWFb5YgMU5dw8p2VKj16S). ⛔ Not claimed, ⛔ not graded, ⛔ no code. I filed objectstack#16411 on this same break from the consuming side before sweeping this repo; this card predates mine by ~15 hours and is better on every axis — the caret-range root cause, the four-version table, and the17.0.0-rc.2control leg that seeds in 30s are all things my single reading did not have. #16411 is closed as a duplicate. ⛔ Nothing from it carries over except the following, which I am recording here rather than as a second card, because whether it deserves one is triage's call and ⛔ not mine.The observation
Same failure, read off objectui CI run
34056438855job101549092958(2026-09-06T19:56–20:03Z, published@objectstack/*@17.2.0). After the auth plugin fails and while nosys_*table exists, the server prints:⚠ AuthPlugin failed to load: The requested module '@better-auth/core/db' does not provide an export named 'createLocalAccountIssuer' … WARN CORE: Core service missing, functionality may be degraded: auth WARN System started with degraded capabilities. Missing core services: auth … ✓ Server is ready ➜ API: http://localhost:4010/ Plugins: 42 loaded Seeds: com.example.showcase 132 rows⇒ the process binds :4010, loads 42 plugins, reports 132 seeded rows, and declares
✓ Server is ready— withauthrecorded missing and not one platform table in existence.Why it is separable from this card
This card describes readiness correctly from the external poller's side (
not ready after 300s, the seeded sign-in never answering). That is a different instrument from the internal banner, and the two disagree. ⇒ the ready signal does not depend on the thing that is broken, so it cannot report it; the only thing that noticed was an outside timeout, and it noticed by giving up rather than by being told.⚠️ Fix the caret range and this seam stays exactly as it is, ready to mis-report the next degraded boot.⭐ Note that this repo already has the correct shape a few lines earlier in the same log —
#8686's tenancy-split probe refuses to report a measured zero when its read failed:NOTE: the sys_organization probe FAILED (… no such table: sys_organization), so the count above is "unknown", not a measured zero — an unreadable probe is reported here rather than through the benign no-organization-yet path (#9261).⇒ that is the template. The readiness banner is the same question answered the other way.
⛔ I am not proposing the repair. 「Make readiness strict」 has an obvious failure mode — a dev machine deliberately running without auth should still boot. The narrow, measured claim is only this: the ready signal and the degraded-capabilities warning are currently independent, and the louder of the two is the one that is wrong.
⚠️ A targeted search of this repo for prior art on a ready-signal-versus-degraded-boot mismatch returned zero; my search of this repo for thecreateLocalAccountIssuerbreak returned this card, #16373 and #16411 — so the zero is a reading on a live instrument, ⛔ not a dark query. Bounded: I did ⛔ not sweepstate=all.Downstream context, consistent with this card's own Downstream section: objectui#8084 carries the consuming-side record, and I have corrected that card where it framed this as an unexplained regression rather than the by-design consequence of objectui#7689's pin realignment.
Generated by Claude Code
17.3.0 confirmed — and it also kills the Console login, not just seeding
The issue's closing line ("
17.3.0is very likely affected too and worth booting before it is recommended to anyone") is now measured rather than expected. Booted 2026-09-07 from a different lane —objectstack-ai/hotclm, a 9-object app on@objectstack/*@17.3.0,requires: ['ui', 'auth'], sqlite driver — and it reproduces exactly as predicted, plus a symptom this issue does not yet record.What a browser sees
The existing report is about the seeding path timing out. Driven through Chromium instead, the failure is user-visible and immediate:
/_console/redirects to/_console/loginand renders the sign-in form normally.- Submitting any credentials answers
Auth request failed with status 404in the form. - Every auth route is absent:
/api/v1/auth/config→ 404,/api/v1/auth/get-session→ 404 (repeatedly, the Console retries). /api/v1/data/<object>→ 401 for every caller, since no session can ever be established.- The banner still prints the ready summary, plus
System started with degraded capabilities. Missing core services: authandSharingServicePlugin: could not enumerate organizations … no such table: sys_organization.
So the blast radius is larger than "never seeds": on 17.3.0 the app cannot be signed into at all, which means a downstream project cannot browser-verify anything it builds. That is how this was found — the maintainer asked for browser verification of a card, and there was no door to walk through.
The fix is not
corealoneDirection 1 in the issue (pin exact) works, but pinning only
@better-auth/coreto 1.7.2 moves the break one layer down rather than closing it:ERROR Failed to register OIDC discovery routes @better-auth/kysely-adapter@1.7.3 imports 'checksSchema' from '@better-auth/core/db/internal' — not exported by core 1.7.2@better-auth/core@1.7.2and@better-auth/kysely-adapter@1.7.3are mutually incompatible in both directions: 1.7.2 hascreateLocalAccountIssuerand lackschecksSchema; 1.7.3 is the reverse. Whichever direction is taken, the whole@better-auth/*family has to move as one unit —better-auth,core, and every adapter/oauth-provider/scim/sso/telemetrysibling.Pinning the full family to 1.7.2 does produce a working boot: auth mounts,
--seed-adminseeds, sign-in succeeds, and an authenticatedGET /api/v1/data/<object>answers 200. That is the shape of a fix, verified end to end, if direction 1 is the one chosen.Downstream state
objectstack-ai/hotclmcarries that family-wide pin as an explicitly temporarypnpm.overridesfixture naming this issue, per its own "platform gaps: report, never patch" rule (objectstack-ai/hotclm#7). It is that project's to delete the moment a fix ships here — no fix is attempted in this repository, and nothing downstream is blocked meanwhile.One observation on direction 3, offered as evidence rather than advocacy: this break has now been found twice, from two unrelated lanes, by someone booting a published artifact for an unrelated reason. Neither time did anything in this repository notice.
Filed from the hotclm PM seat, via Claude Code.
Generated by Claude Code
- added a commit that references this issue
on Sep 7, 2026 - addedpriority:p0Critical: blocker, must ship before MVPCritical: blocker, must ship before MVP
on Sep 7, 2026 Graded p0 and dispatched — maintainer instruction 2026-09-07, 「优先解决」
Triage that was left open on this card:
priority:p0,domain:services, type Bug. Every published 17.x is unusable on a fresh install; that is the top of the scale regardless of how quiet the symptom is.Re-measured on
origin/maintoday — the defect is still live, unchangedpackages/plugins/plugin-auth/package.json @better-auth/core = ^1.7.2 (caret, floats) better-auth, @better-auth/{oauth-provider,scim,sso} = ^1.7.2 packages/plugins/plugin-auth/src/backfill-account-issuer.ts:3 import { createLocalAccountIssuer, … } from '@better-auth/core/db' packages/plugins/plugin-auth/src/backfill-account-issuer.ts:112 export const CREDENTIAL_ISSUER = createLocalAccountIssuer('credential') root package.json pnpm.overrides no better-auth entrySo a release cut from
mainright now reproduces this exactly.Why nobody upstream of the consumers saw it — the part that matters most
pnpm-lock.yamlonmainpins@better-auth/core@1.7.2. Every CI run in this repo therefore resolves the version that still exports the symbol, and is green. The published package declares^1.7.2, and a consumer — who has no lockfile of ours — resolves 1.7.3, which dropped it.The lockfile protects the producer from the defect it is shipping. Pinning the version fixes today's break; it does not fix that. A regression control that resolves the way a consumer resolves is what makes this card stay fixed, and it is part of the dispatch.
Three sightings, one defect, and all three report success while broken
- this card: seeding never happens, four
no such tablesystem objects, sign-in never answers — the CLI logged the real cause as a scrollable oclif warning long before the schema step - Published
@objectstack/*@17.2.0boots without auth:@better-auth/core/dbdoes not exportcreateLocalAccountIssuer, so no platform table is ever created — and the server still prints✓ Server is ready#16411 (closed into this one): published 17.2.0 boots without auth - cli: every command in a scaffolded project prints a Node SyntaxError warning —
@better-auth/core/dbdoes not exportcreateLocalAccountIssuer#16373 (closed into this one just now): every CLI command in a scaffolded project prints the warning and then continues — nothing fails - and on this card, objectui's seat recorded that the server prints
✓ Server is readyon the broken boot
That the failure is silent on every surface is why it survived three published releases. Making the seeding work is therefore not sufficient acceptance: the scaffolded project's CLI must print no such warning, carried over from #16373.
Dispatched with
The immediate fix, and a durable answer to the API change — upstream rolled the issuer-scoped account identity back to opt-in deliberately (better-auth/better-auth#10909, merged 2026-08-24), so restoring a pin is a stopgap and the dev is asked to say plainly which of the two it is delivering. Plus the consumer-resolution control above.
⚠️ This also bears directly on the open release decision: a release cut today ships this. The dev is asked to confirm whethermainis fixed, not merely whether the pinned build passes.- this card: seeding never happens, four
- added 4 commits that reference this issue
on Sep 7, 2026 os-dev-report
{ "issue": 16186, "status": "done", "branch": "claude/issue-16186-better-auth-exact-pin", "pr": "https://github.com/objectstack-ai/objectstack/pull/16634", "premise_still_valid": true, "summary": "Delivered the STOPGAP, labelled as one: plugin-auth pins all five declared better-auth members to an exact 1.7.2, and the pnpm-workspace.yaml override targets move to the same exact version in step, so the version this repo tests is the version a consumer resolves. In-repo resolutions are unchanged (the lockfile already held 1.7.2 for all eleven overridden members). The durable fix is NOT deliverable in this card: reading the installed 1.7.3 shows createLocalAccountIssuer was not renamed but removed with the whole issuer-scoped account identity (accountSchema.issuer gone, AccountKey back to (providerId, accountId), the account.issuer column and its unique index gone from get-tables, accountIssuer zero occurrences in the package), so adopting it means dropping the required sys_account.issuer platform-object column, retiring backfill-account-issuer.ts and two test files, and migrating every deployment. Filed as #16629 with the three options and their costs. Second half delivered too: scripts/check-vendor-export-contract.mjs asserts that a governed vendor's DECLARED range is exact, that the installed version IS that version, and that importing each specifier really yields every symbol our shipped source takes from it; --resolve enumerates every registry version the declared range admits, installs each outside the workspace and fails if any is missing a symbol. Offline leg in lint.yml, --resolve leg in validate-deps.yml (daily). PM-side half-state: the card carries an assignee but NO Claim: comment naming a branch; I posted none (assignee and claim are the PM's) and proceeded, since this is p0 and no competing in-flight work exists on the branch namespace (git ls-remote for claude/issue-16186* was empty before I pushed).", "tests": "MAIN IS BROKEN, measured: a fresh `npm install --no-package-lock` of main's five declared ranges resolves the whole family to 1.7.3 (better-auth, core, oauth-provider, scim, sso, kysely-adapter, telemetry all 1.7.3); a static named import of the two symbols against that tree gives exactly the reported `SyntaxError: The requested module '@better-auth/core/db' does not provide an export named 'createLocalAccountIssuer'` (EXIT=1). ARTIFACT-LEVEL, both directions: plugin-auth's built dist/index.mjs line 825 carries the static import; loaded with its @better-auth/core resolving 1.7.3 -> EXIT=1 with that same SyntaxError, restored to 1.7.2 -> EXIT=0 'LOADED OK'. Mutation and restore both confirmed on disk (mutation: symlink target swapped, version read back 1.7.3; restore: version read back 1.7.2 AND readlink equals the recorded original). ABLATION of the new control (commit first, mutate, restore against HEAD): manifest reverted to main's `^1.7.2`, mutation proved on disk BEFORE any reading (grep counts exact=5/caret=0 before, exact=0/caret=5 after; blob 7ced877e -> 8ab88d1d), then offline leg EXIT=1 ('a governed vendor range must be an EXACT version', both members named) and --resolve leg 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'). Restore proved: restored blob 7ced877e equals the HEAD blob, `git diff HEAD` empty, `git status --porcelain` clean. No build/dist step is involved on either ablation leg (the gate reads manifests, source text and installed packages), so there is no stale-artifact direction to rule out; the plugin-auth dist used in the artifact-level test was rebuilt by `pnpm --filter @objectstack/plugin-auth build` (EXIT=0) immediately before. ACCEPTANCE, each watched running: (1) `bash scripts/publish-smoke.sh` pack mode EXIT=0 end to end — packed every publishable package, scaffolded a project OUTSIDE the workspace, installed from the tarballs, built it, booted `objectstack dev --fresh`; that project carries no better-auth override (only peerDependencyRules.allowedVersions, which moves no resolution) and its family resolved 1.7.2 on all seven members checked; then, read directly off that booted packed scaffold, seeded-admin sign-in returned a token and GET /api/v1/data/{sys_user,sys_organization,sys_permission_set,sys_position} each answered 200 with real seeded rows (admin@objectos.ai, 'Default Organization', a permission set, a position); zero 'no such table', zero SyntaxError, zero 'degraded capabilities', zero 'AuthPlugin failed' in the server log. (2) In that same scaffold, `objectstack --version`, `objectstack validate`, `objectstack build` all EXIT=0 with no SyntaxError, no 'does not provide an export' and no 'Warning:' line at all (#16373's criterion). (3) Control proven to fail — see ABLATION above. UNION at b09fe9d8f on a clean tree: `pnpm --filter '@objectstack/plugin-auth^...' build` 0; `pnpm --filter @objectstack/plugin-auth build` 0; `pnpm --filter @objectstack/plugin-auth test` 102 files / 2158 tests all passed; `pnpm --filter @objectstack/plugin-auth typecheck` 0 (tsc, examples project, check:test-typecheck 10 files / 94 errors / 23 pinned signatures held, unchanged); `pnpm build --concurrency=2` 73/73; `pnpm lint` FULL repo scan (not a narrowing) 6295 files, 0 errors, 0 warnings; `check:vendor-export-contract --resolve` 0; and 25 gates derived from `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` all exit 0 (nul-bytes, agent-test-spelling, entry-guard, parse-guard, watch-hint-literal, doc-authoring, cli-command-ids, cross-package-test-inputs, pnpm-filter-targets, pnpm-acquisition, override-consistency, vendor-export-contract, changeset-gate-self-tests, objectui-changeset, required-contexts, workflow-status-functions, single-claim-paths, pm-dispatch-gates, published-files, manifest-repository-directory, workspace-manifest-cycles, turbo-task-graph, type-check-coverage, type-check-debt, scripts-symbol-anchors). check:entry-guard found a real defect in the first draft of the gate (hand-typed process.argv[1] guard, inert through a symlink at exit 0) and it is fixed in b09fe9d8f. NOT MEASURED, read as neither red nor green: `pnpm check:bash32-floor` exits 1 in its own --self-test on this macOS host because the host bash IS 3.2 and has no `mapfile`, so the harness cannot prove its simulated shell removed a builtin the real shell never had; it fails identically (8 of 153) on the untouched shared main checkout and this diff contains no shell file — CI's ubuntu runner is where that gate is measured. VERIFICATION-LOCK DISCLOSURE: every heavy command was routed through scripts/pm/os-verify-lock.sh, which reported 'UNLOCKED (declared)' on every run (no usable flock on macOS), so nothing was serialized and the wall-clock numbers are shared-box readings.", "mcp_calls": "0 — no MCP GitHub calls; the repo-scoped REST channel was probed and used throughout (gh api / gh issue / gh pr)", "open_questions": [ { "question": "The durable answer to better-auth's account-issuer rollback. 1.7.3 removed the issuer-scoped account identity outright, so the exact pin this PR ships is a stopgap that cannot take a security patch without a reviewed lift. Filed as #16629; recorded here because it is the maintainer's decision, not a blocker for this PR.", "options": [ "A — adopt the rollback: drop sys_account.issuer, retire backfill-account-issuer.ts and account-issuer-parity.test.ts, migrate existing rows off the unique (issuer, account_id) index, raise the family floor to 1.7.3+", "B — keep the 1.7 identity model as ours: declare issuer as an application-owned additional field and resolve accounts on it ourselves instead of through findAccountByKey", "C — stay pinned: the state this PR leaves behind, with the new gate making any lift a reviewed edit" ], "recommendation": "A, because the divergence in B is permanent and unowned upstream while the cost of A is a one-time migration on one table, and C is only correct until the next @better-auth advisory forces a lift that C has no plan for. A is a separate change with its own release note and migration; it should not ride a p0 hotfix." } ], "out_of_scope_findings": [ "filed as #16629: plugin-auth — decide the durable answer to better-auth's account-issuer rollback (1.7.3 removed the issuer identity outright; this PR shipped an exact pin as a stopgap), with the three options, their costs and the measured upstream diff", "filed as #16630 (label `finding`): the server prints the ready banner on a degraded boot, independently of the 'degraded capabilities' warning printed a few lines earlier — carried out of #16186 so it is not buried when that card closes; recorded there by objectui's PM seat, which left the filing decision to triage. No repair proposed." ] }
Generated by Claude Code
Claim: PM session
6679d191-11f4-465b-b322-0e0409d76793(seathotlong), branchclaude/issue-16186-better-auth-exact-pin.Posted late, and the dev was right to flag it: this card carried an assignee and a grading comment but no
Claim:line naming a branch, which is a half-state — the assignee field alone is no proof of a claim, because the identity is shared. The dev proceeded correctly (it checkedgit ls-remotefor the branch namespace first and found it empty). The omission was mine, not a licence for the next reader to assume an unclaimed card.os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched.- Closing pull request: 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, merged.
- Closing commit
2e6a2ea4c9, merged intomain. - Left untouched:
priority:p0,domain:services— ownership, priority and outcome are not state claims. - The label set was read back after the write and matched.
A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.posted by half-state-patrol run 34178100523 · trigger
scheduleGenerated by Claude Code
- added a commit that references this issue
on Sep 9, 2026 - added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Oct 9, 2026
Deliberately unassigned and unlabelled, for triage. Measured from the objectui side while realigning that repo's live-e2e backend pin (objectui#7689 / objectui PR for that card), which is what surfaced it: the pin had been stale for two minor versions, so nothing had booted a published 17.1.0-or-later backend in that lane until today.
Symptom
A fresh install of the published showcase app against
@objectstack/*@17.2.0starts the server and then never seeds. Every system-object read fails:Same for
sys_permission_setandsys_position. The seeded admin sign-in therefore never answers and any caller polling for readiness times out. The driver's own diagnostic says the quiet part out loud a line earlier: "this driver did not create the table".Cause
The CLI logs the real failure at startup, well before the schema step, as an oclif warning that is easy to scroll past:
@objectstack/plugin-authimports that symbol from@better-auth/core/db(present in its shippeddist/index.jsanddist/index.mjs). The installed@better-auth/core@1.7.3does not export it — verified by grep over the installed package tree, zero occurrences.It reaches 1.7.3 through a floating range, and the version that changed is ours, not theirs:
@objectstack/plugin-auth@better-auth/core17.0.0-rc.21.7.0-rc.2— exact, no operator1.7.0-rc.217.1.0^1.7.11.7.317.2.0^1.7.11.7.317.3.0^1.7.21.7.3So the loosening from an exact pin to a caret range is what exposed this, and a
@better-auth/coreminor then removed the export under it.better-authand the sibling@better-auth/*adapters float the same way.Measurement
Both legs run in the same container, minutes apart, through objectui's own
e2e/live/ci/start-backend.sh(sparse-checkout ofexamples/app-showcaseat the release tag's commit, rewrite every@objectstack/*dep to the pinned version,npm install,objectstack dev --seed-admin --fresh, poll the seeded sign-in):@objectstack/*@17.2.0e7d2cc67fdef7fee9d2c6d65d7363fe1c78ce6a4(tag@objectstack/cli@17.2.0)no such tablesystem objects@objectstack/*@17.0.0-rc.289d2a4eb3f3b6b8f8c0fbc4cb3953cbe8218dc66(tag@objectstack/cli@17.0.0-rc.2)createLocalAccountIssuerlines, zerono such tablelinesThe control leg is the load-bearing half: it rules out "this container cannot boot the lane at all" and attributes the failure to the published version, not the harness.
Why it went unseen
Nothing in the repository that consumes published packages pins them by lockfile — the showcase-app boot path installs at run time with no lockfile at all, so each run re-resolves the floating ranges. A published artifact whose dependency ranges float is only as reproducible as npm's registry state that minute, and a consumer's green from last week says nothing about today.
Suggested directions, none picked here
@better-auth/core,better-authand the@better-auth/*adapters to exact versions in@objectstack/plugin-auth, the way17.0.0-rc.2did. Reproducible; costs a release whenever better-auth moves.createLocalAccountIssuerprovided against 1.7.3's actual surface. Fixes today's break, leaves the class open.Whichever is chosen,
17.3.0is very likely affected too and worth booting before it is recommended to anyone.Downstream
objectui's
Live E2E (informational)lane will now go red against the matched pair, by design — a failing matched pair carries strictly more information than a green mismatched one, and objectui#7689's triage forbids repairing it by reverting the pin. That lane is non-blocking, so nothing in objectui is stuck on this; it will simply keep reporting the break until this is addressed.Filed by an objectui developer seat, via Claude Code.