Found while binding the organizations.* return types for #14314 (card 3 of the #12104 family). Out of that card's ruled scope (type narrowing only), so recorded here rather than fixed there. No assignee, no label — left for triage.
The contract the SDK declares
packages/client/src/index.ts, organizations.getActiveMember, JSDoc at the merge base 9407e1865:
Look up the calling user's membership row in the given organisation. Useful for permission checks on the client without having to scan the full member list.
and the method builds GET /api/v1/auth/organization/get-active-member?organizationId=ENCODED_ID.
What the route does
better-auth 1.7.2, plugins/organization/routes/crud-members.mjs, getActiveMember: the handler reads session.session.activeOrganizationId only and never looks at ctx.query. The query string the SDK sends is dead on arrival.
Measured
Real AuthManager (better-auth 1.7.2, organization plugin, teams enabled) over a real SqlDriver (better-sqlite3), one user who owns two organizations with org1 active:
GET /organization/get-active-member?organizationId=ORG1 -> 200 { organizationId: ORG1, role: 'owner', ... }
GET /organization/get-active-member?organizationId=ORG2 -> 200 { organizationId: ORG1, role: 'owner', ... } <- same row, the query was ignored
GET /organization/get-active-member?organizationId=x -> 400 NO_ACTIVE_ORGANIZATION (user with no active organization)
The same three answers came back through the real ObjectStackClient with only the socket stood in for. A caller doing a permission check for organization B while A is active is told about A — the wrong-but-plausible answer, silently.
Class
Declared contract not delivered (the SDK's own JSDoc and its query parameter promise per-organization addressing the runtime does not do). Reproducible with the three calls above.
Options seen, not decided
- SDK: stop sending the parameter and rename or re-document the method as the ACTIVE-organization lookup it is (
#14314's PR already corrects the JSDoc to say the argument is ignored, but keeps the parameter and the request bytes — a body change was out of its scope).
- SDK: implement per-organization lookup honestly by calling
GET /organization/list-members?organizationId=...&filterField=userId&filterValue=SELF and unwrapping the single row, keeping the signature.
- Server: an ObjectStack before-hook that swaps the session's active organization for the request is the wrong shape (it would mutate session state to answer a read).
Refs: #14314 (where it was measured), #12104 (family head).
Generated by Claude Code
Found while binding the
organizations.*return types for #14314 (card 3 of the #12104 family). Out of that card's ruled scope (type narrowing only), so recorded here rather than fixed there. No assignee, no label — left for triage.The contract the SDK declares
packages/client/src/index.ts,organizations.getActiveMember, JSDoc at the merge base9407e1865:and the method builds
GET /api/v1/auth/organization/get-active-member?organizationId=ENCODED_ID.What the route does
better-auth 1.7.2,
plugins/organization/routes/crud-members.mjs,getActiveMember: the handler readssession.session.activeOrganizationIdonly and never looks atctx.query. The query string the SDK sends is dead on arrival.Measured
Real
AuthManager(better-auth 1.7.2, organization plugin, teams enabled) over a realSqlDriver(better-sqlite3), one user who owns two organizations with org1 active:The same three answers came back through the real
ObjectStackClientwith only the socket stood in for. A caller doing a permission check for organization B while A is active is told about A — the wrong-but-plausible answer, silently.Class
Declared contract not delivered (the SDK's own JSDoc and its query parameter promise per-organization addressing the runtime does not do). Reproducible with the three calls above.
Options seen, not decided
#14314's PR already corrects the JSDoc to say the argument is ignored, but keeps the parameter and the request bytes — a body change was out of its scope).GET /organization/list-members?organizationId=...&filterField=userId&filterValue=SELFand unwrapping the single row, keeping the signature.Refs: #14314 (where it was measured), #12104 (family head).
Generated by Claude Code