Found while renaming the two lowercase error codes in register-sso-provider.ts (#10716 / PR #10858). Not fixed there: that card is a casing rename, and this is a behaviour change to which code a condition answers with. Filed rather than absorbed.
The inconsistency
The two SSO domain-verification routes handle the same condition — the feature is off for this environment, so the inner @better-auth/sso endpoint is unmounted and answers 404 with no code — and they answer it with different codes.
packages/plugins/plugin-auth/src/register-sso-provider.ts, runRequestDomainVerification:
if(resp.status===404&&!parsed?.code){return{status: 400,body: {success: false,error: {code: 'DOMAIN_VERIFICATION_DISABLED',message: 'Domain verification is not enabled for this environment (set OS_SSO_DOMAIN_VERIFICATION).'}}};}runVerifyDomain, same condition, a few lines later:
letmessage=parsed?.message||'Domain verification failed';if(resp.status===404&&!parsed?.code){message='Domain verification is not enabled for this environment (set OS_SSO_DOMAIN_VERIFICATION).';}That branch rewrites only the message. The code falls through to the route's generic default, so the disabled case answers 404 + the generic domain-verification-failure code while carrying the "not enabled" copy. The one machine-readable field says "verification failed"; the human-readable one says "the feature is off". They are different answers and a client can only act on the first.
This predates #10716 and is unchanged by it
Before that PR the default was the bespoke verify_domain_failed; after it, the registered DOMAIN_VERIFICATION_FAILED. Both spell "failed", neither spells "disabled" — so the rename neither introduced nor repaired this. It is named here because the rename makes it easy to see: the same file now has DOMAIN_VERIFICATION_DISABLED and DOMAIN_VERIFICATION_FAILED sitting one function apart, answering one condition two ways.
Why it is worth fixing rather than tolerating
DOMAIN_VERIFICATION_DISABLED is already registered for @objectstack/plugin-auth (packages/spec/src/api/error-code-ledger.zod.ts:340, "domain verification is off on this deployment"), so the correct code exists and costs no ledger change. A caller that wants to say "turn on OS_SSO_DOMAIN_VERIFICATION" rather than "retry, DNS may not have propagated" cannot currently tell those apart on verify-domain, only on request-domain-verification.
Also worth deciding as part of it: whether the status should stay 404 (the sibling rewrites it to 400) — the two routes differ there too, and one answer for one condition is the point.
What it would take
runVerifyDomain's resp.status === 404 && !parsed?.code branch stamps DOMAIN_VERIFICATION_DISABLED alongside the message it already rewrites.- Decide the status question above.
- The dogfood probe note at
packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts:252 describes this exact response ("admin passes the gate and lands on 404 … while SSO is off") and would need updating with whatever is decided. ⚠️ That note is documentation, not an assertion — the bucket loop only asserts the code is not PERMISSION_DENIED — so nothing goes red on its own here. - It is a wire-visible change to a published endpoint, so contract-review tier.
Refs
#10716 · PR #10858 (where this surfaced) · #10534 (the SSO mount documentation card) · ADR-0112
Found while renaming the two lowercase error codes in
register-sso-provider.ts(#10716 / PR #10858). Not fixed there: that card is a casing rename, and this is a behaviour change to which code a condition answers with. Filed rather than absorbed.The inconsistency
The two SSO domain-verification routes handle the same condition — the feature is off for this environment, so the inner @better-auth/sso endpoint is unmounted and answers
404with no code — and they answer it with different codes.packages/plugins/plugin-auth/src/register-sso-provider.ts,runRequestDomainVerification:runVerifyDomain, same condition, a few lines later:That branch rewrites only the message. The code falls through to the route's generic default, so the disabled case answers
404+ the generic domain-verification-failure code while carrying the "not enabled" copy. The one machine-readable field says "verification failed"; the human-readable one says "the feature is off". They are different answers and a client can only act on the first.This predates #10716 and is unchanged by it
Before that PR the default was the bespoke
verify_domain_failed; after it, the registeredDOMAIN_VERIFICATION_FAILED. Both spell "failed", neither spells "disabled" — so the rename neither introduced nor repaired this. It is named here because the rename makes it easy to see: the same file now hasDOMAIN_VERIFICATION_DISABLEDandDOMAIN_VERIFICATION_FAILEDsitting one function apart, answering one condition two ways.Why it is worth fixing rather than tolerating
DOMAIN_VERIFICATION_DISABLEDis already registered for@objectstack/plugin-auth(packages/spec/src/api/error-code-ledger.zod.ts:340, "domain verification is off on this deployment"), so the correct code exists and costs no ledger change. A caller that wants to say "turn onOS_SSO_DOMAIN_VERIFICATION" rather than "retry, DNS may not have propagated" cannot currently tell those apart onverify-domain, only onrequest-domain-verification.Also worth deciding as part of it: whether the status should stay
404(the sibling rewrites it to400) — the two routes differ there too, and one answer for one condition is the point.What it would take
runVerifyDomain'sresp.status === 404 && !parsed?.codebranch stampsDOMAIN_VERIFICATION_DISABLEDalongside the message it already rewrites.packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts:252describes this exact response ("admin passes the gate and lands on 404 … while SSO is off") and would need updating with whatever is decided.PERMISSION_DENIED— so nothing goes red on its own here.Refs
#10716 · PR #10858 (where this surfaced) · #10534 (the SSO mount documentation card) · ADR-0112