Skip to content

[finding] rethrowAsBetterAuthError maps engine errors code-by-code, so each new engine code needs its own incident before it stops leaking as a bodyless 500 #7881

Description

@huangyiirene

Filed by the domain:identity seat (#6022) out of PR #7879's review, on that dev's own reported observation (open question 1). Observation-class — nothing is broken today, which is why this is a finding and not a queue card. Unassigned; triage grades it.

The pattern

rethrowAsBetterAuthError in packages/plugins/plugin-auth/src/objectql-adapter.ts converts engine faults into better-auth APIErrors so they reach the client as a structured envelope. It matches by signature, arm by arm:

armadded byengine fault
validation errorspre-existingfield/schema validation
policy refusalspre-existingauthorization
DELETE_RESTRICTEDPR #7879 (#7724)referential restrict

Anything not matching an arm falls through to throw err, and better-auth's router renders that as HTTP 500 with an empty body.

Two incidents so far, both the same shape, both found the expensive way:

Each was diagnosed by a dev reading better-auth source to work out why the body was empty. Neither was found by a gate.

Why it is worth recording rather than fixing now

#7879's dev deliberately kept its arm narrow (code === 'DELETE_RESTRICTED') and reported the generalisation instead of taking it — correct under that card's scope ruling, and correct on the evidence: two measured codes is thin ground for remapping every better-auth route under one predicate.

But the shape is worth naming: the engine already declares a numeric status on these errors. A rule like "any engine error carrying a 4xx status maps to the matching APIError" would consume what the producer already states, instead of re-enumerating it downstream — the contract-first direction. As it stands, the third engine code will need a third incident, and the incident is a silent one: an empty body is the hardest failure to attribute, and in both cases so far it was hiding a state change that had already happened.

⚠️ Before acting on this, measure the blast radius

Not a cleanup. Widening the predicate changes the response shape of every better-auth route at once, so it needs:

Related

#7724 / PR #7879 (the DELETE_RESTRICTED arm and the source-level diagnosis) · #7725 / PR #7796 (the UNIQUE-violation instance; its symptom is resolved at the invitation layer, but the mapping class was left alone) · #7880 (the other finding from the same review)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions