Skip to content

fix(auth): add the account.issuer field better-auth 1.7 expects - #136

Merged
KenTaniguchi-R merged 1 commit into
mainfrom
fix/auth-account-issuer-drift
Sep 1, 2026
Merged

fix(auth): add the account.issuer field better-auth 1.7 expects#136
KenTaniguchi-R merged 1 commit into
mainfrom
fix/auth-account-issuer-drift

Conversation

@KenTaniguchi-R

Copy link
Copy Markdown
Owner

Fixes #133.

Email signup returned 500 for every new account:

[Better Auth]: The field "issuer" does not exist in the "account" Drizzle schema.

better-auth 1.7 added account.issuer — it namespaces an identity to the
authority that issued it, so a provider ID can't collide across authentication
methods. The drizzle adapter validates its models against our schema at request
time, so a field we never regenerated showed up as a runtime 500 rather than a
build failure. Only new signups touched the model, which is why existing sessions
kept working and nobody noticed.

Is anything else drifted?

No — issuer is the only one. I computed getAuthTables() for the installed
better-auth (core models plus the passkey plugin's) and diffed it against every
declared table: 64 field assertions were already green, 2 failed, both on
account.issuer.

Why NOT NULL, and why the backfill is load-bearing

issuer isn't just written, it's filtered on. Sign-in matches
account.issuer === 'local:credential', and findCredentialAccount /
updatePassword put it in the WHERE clause. A NULL issuer is therefore an
account nobody can log into — worth a database constraint, not just a
convention.

That makes the migration the risky part. pnpm db:generate emitted:

ALTER TABLE "account" ADD COLUMN "issuer" text NOT NULL;

Run against the dev DB (in a rolled-back transaction):

ERROR:  column "issuer" of relation "account" contains null values

Shipped as generated, that would crash-loop every existing self-hosted install
on container startup. 0009 instead adds nullable → backfills → enforces
NOT NULL, the same shape 0005 already uses. Backfill is local:credential,
with a CASE covering a fork that added a social provider before upgrading.

The migration is newly generated — no existing migration was hand-edited or
re-timestamped, and the journal diff is purely additive.

Error surfacing

Both auth forms collapsed every failure into "Something went wrong. Please try
again." — advice that can never work for a server-side fault, and the reason
#133 sat behind a dead end while the server was logging the exact cause. A 5xx
now says it's a server problem and points at the logs, which is the only signal
a self-hoster has. It deliberately does not echo the server's message on a 5xx.
Login had the identical swallow, so it got the same fix.

Regression test

src/db/schema/auth.test.ts asserts the whole expected model set against the
tables we declare. It mirrors the adapter's real resolution — schema[modelName][fieldName]
by JS property key, not SQL column name — and is driven by auth.options, so a
plugin added to src/lib/auth/index.ts later is covered without touching the
test. More durable than an end-to-end signup test, and it fails at the exact
field.

Verification

  • Migration applied to the dev DB: all 5 existing accounts backfilled, column NOT NULL.
  • Live server: signup 200, sign-in 200, wrong password 401.
  • The 5 pre-existing rows are identical to a freshly written one on every field
    sign-in matches against (provider_id, issuer, account_id = user_id) — so
    existing users are not locked out.
  • pnpm typecheck clean, pnpm lint clean, pnpm test 1027 passed / 1 failed.

The one failure is investment-queries.test.ts > returns dayChange from holdings_history, which already fails on clean main — it pins hardcoded
2026-05-09/2026-05-10 dates against a now-relative window. Unrelated to this
change; worth its own issue.

🤖 Generated with Claude Code

Every email signup 500d with `The field "issuer" does not exist in the
"account" Drizzle schema`. better-auth 1.7 added `account.issuer`, which
namespaces an identity to the authority that issued it, and the drizzle
adapter validates its models against our schema at request time — so a
field we never regenerated surfaced as a runtime error, not a build one.
Only new signups hit it, which is why it went unnoticed.

`issuer` is the only drift. Checked by computing `getAuthTables()` for
the installed version against every declared table, core and passkey
plugin alike; the other 64 field assertions were already green.

The column is NOT NULL rather than nullable because sign-in *filters* on
it (`issuer = 'local:credential'` in the credential-account lookup), so a
NULL issuer is an account nobody can log into. That makes the backfill
load-bearing: drizzle-kit generated a bare `ADD COLUMN ... NOT NULL`,
which fails outright on any populated table — verified against the dev
DB, `column "issuer" of relation "account" contains null values`. Every
existing install would have crash-looped on startup. 0009 instead adds
nullable, backfills, then enforces NOT NULL, the same shape 0005 uses.

Also stop both auth forms from collapsing a 500 into "Something went
wrong. Please try again." — advice that can never work for a server-side
fault. A 5xx now says it is a server problem and points at the logs,
which is the only signal a self-hoster has.

Regression coverage is a model-shape assertion driven by `auth.options`,
so a plugin added later is checked without editing the test — more
durable than an end-to-end signup test, and it fails at the exact field.

Verified: signup 200, sign-in 200, wrong password 401, and the five
pre-existing accounts backfilled to rows byte-identical to a freshly
written one on every field sign-in matches against.
@KenTaniguchi-R
KenTaniguchi-R merged commit 611ca69 into main Sep 1, 2026
5 checks passed
@KenTaniguchi-R
KenTaniguchi-R deleted the fix/auth-account-issuer-drift branch September 1, 2026 23:51
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.

Email signup 500s: Better Auth expects an "issuer" field the account schema does not have

1 participant