Found while re-pinning the release-candidate smoke for #18079. Not fixed there — that card's lane is domain:devx and its file surface is the smoke driver only.
What changed
#17881 (374d9d3afa, 2026-09-12T19:15:14Z) landed packages/plugins/plugin-auth/src/anonymous-session-refusal.ts, which converts the anonymous GET /api/v1/auth/get-session answer from 200 + the literal JSON null into the ADR-0112 refusal envelope with HTTP 401 and code: UNAUTHENTICATED. That module's own header states the scope precisely:
⛔ It does not touch better-auth's JS API. auth.api.getSession() — the seam resolve-execution-context.ts, the /admin/ gates and the SSO bridges read — still answers null for an anonymous caller, because that is a function return value and not an HTTP answer. Only the wire shape moves.
packages/client reaches the server over the wire, so the wire shape is exactly what it sees.
What was left behind
Two present-tense statements about the wire still describe the retired shape. Neither carries a rev or a date, so neither reads as history.
-
packages/client/src/index.ts:1474 — inside a block introduced as "these are the three bodies this helper is handed":
GET /api/v1/auth/get-session (anonymous) -> 200 null
The helper is no longer handed that. A reader coding against this table writes a null branch that can never be taken and omits the rejection branch that now fires.
-
packages/client/src/organization-get-active-member-addressing.test.ts — the test double models the same retired shape, in the header table at :40 and in the option docblock at :88:
/** `false` models an anonymous caller: `/get-session` answers the literal `null`. */
signedIn?: boolean;
This is the more consequential half. organizations.getActiveMember reads the caller's own user id from /get-session as step 1 of its two-request self-membership lookup (auth-route-ledger.ts:158). The signedIn: false leg therefore exercises a server answer the product no longer produces, so whatever it pins about the anonymous path is pinned against a fake the runtime has stopped matching.
Why this is filed rather than noted
The SDK's docblock is that helper's own statement of the contract it consumes, and it now contradicts the shipped runtime — the declared-contract class. The test double is the phantom-check shape AGENTS.md warns about: it cannot fail for the reason it exists, because the condition it models is unreachable.
What I did NOT establish
- Whether
auth.me()'s rejection path is covered anywhere today. I did not measure it; I only measured that the wire answer moved and that these two statements did not.
- Whether any other consumer branches on the anonymous
null. I grepped get-session across packages, scripts, docs, content and .github and triaged the hits; the ones above are the two that are both present-tense and about the wire. packages/adapters/hono and packages/verify also carry -> 200 {} lines for a different base path, which I did not chase.
- The correct disposition. Updating the docblock is mechanical; what the test double should model instead is a judgement for the seat that owns the auth surface, since it decides whether the anonymous leg should now assert the refusal or be dropped.
Dedupe
Searched the open set (recent page, 94 open non-PR issues, numbers 17723..18137) on get-session, auth.me(, SessionResponse, #17238, #17881 across titles and bodies. The only match was #18079 itself. Controls: a term certain to match returned 94, a nonsense term returned 0. ⚠️ That page does not reach open issues numbered below 17723, so this is a recent-window dedupe, not an exhaustive one. Closed and related: #17238 (the card that ruled the change), #16760 (the /get-session envelope as the client SDK declares it).
Filed by the os-dev seat working #18079, session_012GKcPZbMoGq7WPzKLfRBTU. This seat did not touch the auth surface or the client SDK — routing is the triage seat's.
Found while re-pinning the release-candidate smoke for #18079. Not fixed there — that card's lane is
domain:devxand its file surface is the smoke driver only.What changed
#17881 (
374d9d3afa, 2026-09-12T19:15:14Z) landedpackages/plugins/plugin-auth/src/anonymous-session-refusal.ts, which converts the anonymousGET /api/v1/auth/get-sessionanswer from200+ the literal JSONnullinto the ADR-0112 refusal envelope with HTTP 401 andcode: UNAUTHENTICATED. That module's own header states the scope precisely:packages/clientreaches the server over the wire, so the wire shape is exactly what it sees.What was left behind
Two present-tense statements about the wire still describe the retired shape. Neither carries a rev or a date, so neither reads as history.
packages/client/src/index.ts:1474— inside a block introduced as "these are the three bodies this helper is handed":The helper is no longer handed that. A reader coding against this table writes a
nullbranch that can never be taken and omits the rejection branch that now fires.packages/client/src/organization-get-active-member-addressing.test.ts— the test double models the same retired shape, in the header table at:40and in the option docblock at:88:This is the more consequential half.
organizations.getActiveMemberreads the caller's own user id from/get-sessionas step 1 of its two-request self-membership lookup (auth-route-ledger.ts:158). ThesignedIn: falseleg therefore exercises a server answer the product no longer produces, so whatever it pins about the anonymous path is pinned against a fake the runtime has stopped matching.Why this is filed rather than noted
The SDK's docblock is that helper's own statement of the contract it consumes, and it now contradicts the shipped runtime — the declared-contract class. The test double is the phantom-check shape
AGENTS.mdwarns about: it cannot fail for the reason it exists, because the condition it models is unreachable.What I did NOT establish
auth.me()'s rejection path is covered anywhere today. I did not measure it; I only measured that the wire answer moved and that these two statements did not.null. I greppedget-sessionacrosspackages,scripts,docs,contentand.githuband triaged the hits; the ones above are the two that are both present-tense and about the wire.packages/adapters/honoandpackages/verifyalso carry-> 200 {}lines for a different base path, which I did not chase.Dedupe
Searched the open set (recent page, 94 open non-PR issues, numbers 17723..18137) on⚠️ That page does not reach open issues numbered below 17723, so this is a recent-window dedupe, not an exhaustive one. Closed and related: #17238 (the card that ruled the change), #16760 (the
get-session,auth.me(,SessionResponse,#17238,#17881across titles and bodies. The only match was #18079 itself. Controls: a term certain to match returned 94, a nonsense term returned 0./get-sessionenvelope as the client SDK declares it).Filed by the
os-devseat working #18079,session_012GKcPZbMoGq7WPzKLfRBTU. This seat did not touch the auth surface or the client SDK — routing is the triage seat's.