Repository navigation
auth: plugin-auth doors that re-dispatch get-session or call a better-auth endpoint in-process still renew a cookie session without re-issuing its cookie (split session, the residue of #22258) #22398
Description
Activity
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsTriage: first grade,
priority:p2·domain:services·area:identity·bug·pm:blockedon PR #22396. This card closes #22258's familyTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T03:00Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/plugins/plugin-auth. The files areregister-sso-provider.ts,send-verification-email.ts,organization-add-member.ts,set-initial-password.tsandauth-plugin.ts⇒domain:services.- Why p2: the same split session as auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258 (a renewed session whose cookie is never re-issued), measured at four public doors. It happens before each door's own refusal, so even a refused call renews.
- Blocked on PR fix(auth,services): the services-lane in-process session reads stop renewing a cookie session (#22258) #22396 (auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258's
domain:serviceshalf). It editsauth-plugin.tstoo, so cut this card after it lands, onmain:
Blocked-by: #22396
- This card closes the family. Widen the card's enumeration pin from
packages/plugins/**andpackages/services/**to all ofpackages/**.- Count every in-process better-auth call that carries request headers:
handler(re-dispatches of/get-session, andauthApi.*/api.*calls withheaders, under any receiver spelling. - List every hit on the PR as fixed, as not renewing (with the reason), or as another lane's.
- A hit in another lane goes on its own card from this PR, so a third card does not come from a missed site.
- Count every in-process better-auth call that carries request headers:
- Direction: the card's own. Its pins and ablations stand as written.
- The cookie-conditional rule carries over: a cookie request does not renew in-process, and a bearer-only one keeps renewing.
- ⛔ No change to
inProcessSessionReadInput's semantics.
- addedarea:identityLogin and identity — sign-up, sessions, organization membership, SSOLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Oct 9, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsUnlock scan:
pm:blocked→pm:queue·domain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-09T09:43Z- Blocker gone: PR fix(auth,services): the services-lane in-process session reads stop renewing a cookie session (#22258) #22396 (
Blocked-by: #22396, triage6073355679) merged as4f4c4ed819, onorigin/main. - Premise re-checked on the merged
main: every residue site this card names is still present:- the
/get-sessionre-dispatch inregister-sso-provider.ts(thesessionUrlrewrite) and insend-verification-email.ts; authApi.addMemberinorganization-add-member.tsandauthApi.setPasswordinset-initial-password.ts;authApi.createOAuthClientinauth-plugin.ts(about:3170).- PR fix(auth,services): the services-lane in-process session reads stop renewing a cookie session (#22258) #22396 changed none of these.
- the
auth-plugin.ts's serial constraint is lifted: PR fix(auth,services): the services-lane in-process session reads stop renewing a cookie session (#22258) #22396's four reader edits there are onmain.- Triage's direction stands as written in
6073355679: the enumeration pin spans all ofpackages/**, and any hit in another lane goes on its own card from this PR.
Generated by Claude Code
- Blocker gone: PR fix(auth,services): the services-lane in-process session reads stop renewing a cookie session (#22258) #22396 (
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01WYYhVJ78u7PhwFViWo1EmQ
Account:os-elon-musk(the seat's linked user asget_meanswers it; the card's assignee)
Branch:claude/issue-22398-plugin-auth-session-redispatch
Worktree:objectstack-issue-22398
Domain:domain:services
Seat:domain:services#2(seat post #21118)
File surface, per the card body and triage6073355679, read onorigin/mainafter PR #22396 (4f4c4ed819):packages/plugins/plugin-auth/src/register-sso-provider.ts: the/get-sessionre-dispatch (thesessionUrlrewrite, about:53) and the SSO bridges' inner re-dispatches (about:200–:460).packages/plugins/plugin-auth/src/send-verification-email.ts: its/get-sessionre-dispatch (about:63).packages/plugins/plugin-auth/src/organization-add-member.ts(authApi.addMember, about:175) andset-initial-password.ts(authApi.setPassword, about:65): in-process vendor endpoint calls carrying the request headers.packages/plugins/plugin-auth/src/auth-plugin.ts: theauthApi.createOAuthClientcall (about:3170) only.- A shared helper in
plugin-authif one rule shape fits several sites, its pins, and new pin test files inplugin-auth. .changeset/22398-*.md:minorfor@objectstack/plugin-auth(amended in place with theClause-②line below; it first readpatch).- ⛔ No
packages/typeschange toinProcessSessionReadInput's semantics (triage); no other package's source. A hit of thepackages/**enumeration in another lane is reported, not edited: the seat files it. ⛔ Nopackages/spec, nocontent/docs. (Stop on breach and explain in the report.)
Container & model:M,mode:subagent,model: default—dispatch-gates --tiergives no path-derived mandate; choosing a rule shape per site (a re-dispatch vs a vendor endpoint call) and pinning it against real better-auth is judgment work.
Clause-②: yes (widening) - Amended in place at review of PR fix(auth): plugin-auth re-dispatch and vendor-call doors stop renewing a cookie session in-process (#22398) #22461:
SetPasswordCapableApiis published (plugin-auth'sindex.tsre-exportsset-initial-password.js), and itssetPasswordoptions gain an optionalquerykey. That is an additive widening of a published surface, so it takesminor(the WHICH LEVEL ruling) and is owed a contract-review-tier record on the head. The doors keep their inputs and answers; only whether a cookie request renews the session in-process changes. (The claim first readno; that was the seat's misreading of the published surface.)
Responsibility:platform code: four plugin-auth doors (sso/register, send-verification-email, organization/add-member, set-initial-password) renew a cookie session in-process through a /get-session re-dispatch or a vendor endpoint call, and drop the renewed Set-Cookie|the cookie-conditional rule of inProcessSessionReadInput covers getSession readers only; no rule covers a handler re-dispatch or an in-process vendor endpoint call|any user active past updateAge who calls those doors (measured at four public doors on PR #22396's build, report 6073006438), after which cookie-only paths see them signed out
Thread-read: 6078438604
Serial constraints cleared: read 2026-10-09T09:45Z: - Open PRs (12): none touches any of the five files.
- In-flight claims in
domain:services: feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196 (seat 1,plugin-security): disjoint.auth-plugin.ts's region row on [PM seat] domain:services — 🟢 zhuangjianguo · session_013j5gkUCpqQiti4GgPqqmnt #6021 holds no open claim; PR fix(auth,services): the services-lane in-process session reads stop renewing a cookie session (#22258) #22396's edits there are onmain.
domain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-09T09:45Zobjectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22398,
"status": "done",
"branch": "claude/issue-22398-plugin-auth-session-redispatch",
"pr": "#22461",
"session": "session_01WYYhVJ78u7PhwFViWo1EmQ (the dispatching PM's session; mode:subagent)",
"premise_still_valid": true,
"summary": "Ten in-process session reads at eight plugin-auth doors now follow the cookie-conditional rule. Six are handler re-dispatches: the /get-session lookups in register-sso-provider.ts:64 and send-verification-email.ts:66, and the bridges' forwards to /sso/register (OIDC :210, SAML :308), /sso/request-domain-verification (:413), /sso/verify-domain (:465) and /send-verification-email (:135). Each re-dispatched URL gains disableRefresh=true through a new helper, in-process-redispatch.ts (inProcessRedispatchUrl, built on carriesSessionCookie). The other three are vendor endpoint calls: addMember (organization-add-member.ts:176), setPassword (set-initial-password.ts:76) and createOAuthClient (auth-plugin.ts:3173). Each spreads inProcessSessionReadInput(request.headers) in place of headers. All five mechanism assumptions held on real better-auth 1.7.3. /get-session coerces the query flag. getSessionFromCtx, used by sessionMiddleware and by AuthManager's before-hooks, spreads the route's ctx.query into its read, and none of the re-dispatched routes or the three endpoints declares a query schema that would strip it. inProcessSessionReadInput's semantics are untouched, with no packages/types change. Before the change, all eight doors moved expires_at +86460 s by cookie and set no cookie. That includes createOAuthClient, SAML register and both domain-verification bridges, which the card had only by source. After the change, each leaves expires_at unchanged with no cookie by cookie, and still renews to now + expiresIn with no cookie bearer-only. The packages/** census finds no unconverted hit in another lane, so the seat owes no other-lane card.",
"tests": "Readings at feea8a8 unless stated otherwise. (1) Build: turbo run build --filter='@objectstack/plugin-auth^...' --concurrency=2 gave 27/27; pnpm --filter @objectstack/plugin-auth build exited 0; the full turbo run build --filter='!@objectstack/docs' gave 72/72 (71 cached). (2) pnpm --filter @objectstack/plugin-auth typecheck exited 0: tsc, the examples config, and check:test-typecheck '10 file(s) / 94 error(s) / 23 pinned signature(s) held', unchanged. tsc --listFilesOnly lists the pin file and register-sso-provider.test.ts under tsconfig.test.json, and in-process-redispatch.ts under tsconfig.json. (3) pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2: 'Test Files 133 passed (133)', 'Tests 2711 passed | 10 skipped (2721)'. (4) Pin: src/in-process-session-renewal.pin.test.ts, the #22258 real-better-auth harness extended (real AuthManager, real registerAuthRoutes on Hono, session aged to now + expiresIn - updateAge - 60 s). It covers 9 door shapes x 2 cases. Each door asserts the answer that proves its last in-process read ran: register 403 SSO_REGISTER_FAILED, register-saml 403 SAML_REGISTER_FAILED, both domain bridges 403, send-verification-email (body {} and {email}) 400 EMAIL_ALREADY_VERIFIED, add-member 400 ORGANIZATION_NOT_FOUND, set-initial-password 409 PASSWORD_ALREADY_SET, oauth register 200. File alone: 39 passed. Runtime on a shared box: before, 21 tests, 'tests 4.66s', 'Duration 19.87s'; after, 39 tests, 'tests 4.72s', 'Duration 18.66s'. No new sibling test file is imported: the cases live in the file that already imports createMemoryEngine. (5) Ablation: ten legs, run at b9b04ef, which is source-identical to the head (feea8a8 adds only the changeset). Each leg went through scripts/ablation-replace.mjs in WRAP mode with a literal anchor hit 1 -> 0 and a changed blob, inside a driver whose trap restored every touched file by absolute path. Predicted direction: red on exactly the named door's cookie case(s), bearer controls green. Observed: exactly that on every leg. L1 sso get-session re-dispatch: '2 failed | 37 passed (39)' (register and register-saml by cookie). L2 OIDC inner, L3 SAML inner, L4 request-domain, L5 verify-domain, L6 send-verification get-session, L8 addMember, L9 setPassword, L10 createOAuthClient: '1 failed | 38 passed (39)' each, the named door. L7 send-verification inner: '2 failed | 37 passed (39)'. Message on every leg: 'the session renewed (+86460 s) but its cookie was not re-issued'. Each leg was restored to blob == HEAD with git diff HEAD empty: register-sso-provider.ts 7e90a28d0efe, send-verification-email.ts abdc89488528, organization-add-member.ts 124215441d17, set-initial-password.ts 590471136670, auth-plugin.ts 3235e929a46b. The trap re-proved all five == HEAD. Every mutated file reaches the pin through relative src imports, so no dist leg applies. (6) Lint, narrowed and declared, covered by three facts. Population, read from eslint's own config: the 8 changed .ts files; the changeset answers 'File ignored because no matching configuration was supplied'. Count: eslint --no-inline-config --format json linted 8 files with 0 errors and 0 warnings. Invariance: eslint.config.mjs has no parserOptions.project and no typed rules (its comment at :327-328 says so), so this diff cannot move any untouched file's verdict. The repo-wide pnpm lint is CI's. (7) Census, re-runnable: a TypeScript-AST scan of 7,970 non-test sources under packages/. It finds A=18 api-receiver calls with headers, B=57 handle/handleRequest/handler calls (19 better-auth), and C=40 getSession calls. Every hit is listed on the PR as fixed here, converted (#22258), not renewing (with the reason), or not better-auth.",
"gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 68 commands at feea8a8 from 9 changed paths (+295/-16). Each ran with its exit code recorded, and all 68 exit 0. check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: no dist/ for 39 packages) and exits 0 after the full build. The dist-reading gates were re-run after the full build, all exit 0: check:dts-closure (72 packages), check:sourcemap-no-sources-content (68), check:lean-entry-closure and check:published-files. --ran: 'Run reconciliation — 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN.' and '✓ dispatch-gates --ran: 68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero ...)', exit 0. PR CI, read once at feea8a8: 12 check runs completed with 0 failures and 19 in_progress. Left in_progress; not waited on.",
"line_budget": "9 files, +295 / -16 against merge base 2b61f2d. Shipped source, +104 / -13: auth-plugin.ts +5/-2, in-process-redispatch.ts +53 (new), organization-add-member.ts +9/-1, register-sso-provider.ts +16/-5, send-verification-email.ts +7/-2, set-initial-password.ts +14/-3. Tests: the pin +169/-1 and register-sso-provider.test.ts +5/-2. The changeset adds +17. Under the 5,000-line threshold. No governed surface.",
"files_changed": [
".changeset/22398-plugin-auth-session-redispatch.md",
"packages/plugins/plugin-auth/src/auth-plugin.ts",
"packages/plugins/plugin-auth/src/in-process-redispatch.ts",
"packages/plugins/plugin-auth/src/in-process-session-renewal.pin.test.ts",
"packages/plugins/plugin-auth/src/organization-add-member.ts",
"packages/plugins/plugin-auth/src/register-sso-provider.test.ts",
"packages/plugins/plugin-auth/src/register-sso-provider.ts",
"packages/plugins/plugin-auth/src/send-verification-email.ts",
"packages/plugins/plugin-auth/src/set-initial-password.ts"
],
"deviations": [
"File surface: one file beyond the planned set, packages/plugins/plugin-auth/src/register-sso-provider.test.ts (+5/-2). Its SAML bridge case pinned the inner URL as .../sso/register for a request carrying a session cookie, which is the renewing call. The case now expects .../sso/register?disableRefresh=true. That is the rule itself, so I did not stop on it.",
"The enumeration pin is the census on the PR body, not a committed test. That is the form #22258's triage 6072029812 used ('the PR re-runs the census ... lists every hit'). A committed packages/ scan inside plugin-auth would need a CROSS_PACKAGE_TEST_INPUTS declaration of all of packages/** (scripts/ plus turbo.json, outside the file surface), and would re-run plugin-auth's 133-file suite on any PR touching any package. If the seat reads triage 6073355679's 'enumeration pin' as a committed test, that is a new-gate-shaped decision for the seat, not something I added.",
"The pin cases went into the existing in-process-session-renewal.pin.test.ts rather than a new file. A new file would import impersonation-bearer-rotation.test.ts again for createMemoryEngine and re-run its 10 cases. The fixture was widened (sso + ssoDomainVerification + oidcProvider + emailVerification on, the admin's email marked verified, one member-owned org-less sys_sso_provider row), and the #22258 cases stay green on it.",
"The branch is not merged with origin/main. The one commit since the base, 3ca71b6, touches only metadata-protocol, nothing in plugin-auth or types. CI tests the merge ref.",
"Ablation readings are at b9b04ef. The final head feea8a8 adds only the changeset, so the source is identical. Gate, typecheck, test and lint readings are at feea8a8.",
"Attribution follows AGENTS.md. Commit trailers are the model-free pair (Claude-Session + 'Co-authored-by: Claude'); the harness reminder's model-named Co-Authored-By line was not used. The PR body footer is the session-URL form.",
"A throwaway exploration test lived uncommitted in plugin-auth/src during measurement. It was moved out to the scratchpad before the first commit and was never pushed."
],
"mcp_calls": "0 (no MCP tool called in this run)",
"api_writes": "3. Each was one repository_dispatch through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches). (1) pr_create became POST /repos/objectstack-ai/objectstack/pulls: draft #22461, read back 19384/19384 bytes identical. (2) label-write --assign os-elon-musk became POST /repos//issues/22461/assignees, and the read-back matches. The labels documentation, size/m, tests and tooling are other actors'. (3) post-stamped became POST /repos//issues/22398/comments (this report). Not REST: git push of the branch, an empty-branch probe plus 3 commit pushes, all fast-forward, no force. Reads: gh api GETs of the card, its comments, comment 6073006438, triage 6072029812, PR #22461 and its check runs.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: the mounted HTTP route, measured with the real plugin-auth registerAuthRoutes on Hono, a real AuthManager on better-auth 1.7.3, and sso({ domainVerification: { enabled: true } }) (plugins.ssoDomainVerification). With domain verification ON, POST /api/v1/auth/admin/sso/request-domain-verification and POST /api/v1/auth/admin/sso/verify-domain with an unknown providerId, sent by an admin, answer 400 DOMAIN_VERIFICATION_DISABLED, 'Domain verification is not enabled for this environment (set OS_SSO_DOMAIN_VERIFICATION)'. That is user-visible copy telling the operator to turn on a feature that is already on. · evidence: the vendor answers 404 {"message":"Provider not found"} (checkProviderAccess, @better-auth/sso 1.7.3 dist/index.mjs:2259), with no code; feature off is a 404 with an empty body (both read with handleRequest directly). The bridges in plugin-auth/src/register-sso-provider.ts map any 404 with no code to DISABLED (resp.status === 404 && !parsed?.code, runRequestDomainVerification and runVerifyDomain, :417 and :480 at feea8a8). Not this card's defect class, so not fixed here. Same lane (domain:services, plugin-auth). · dedupe words: DOMAIN_VERIFICATION_DISABLED provider not found, sso request-domain-verification verify-domain 404 mapping, domain verification not enabled misleading",
"carrier: none · Observed while building the fixture: with email verification on and no email service wired, POST /api/v1/auth/send-verification-email for an unverified user answers 500 {"success":false}. The 'no email service is configured' reason AuthManager throws reaches the server log only, because better-auth answers a thrown non-API error with an empty 500 body. A misconfiguration path, not investigated further · noted in Acceptance notes, not filed",
"carrier: none · Census result for the seat: no hit of the packages/** enumeration outside plugin-auth is unconverted. Every other-lane in-process better-auth read already carries inProcessSessionReadInput (#22258's PRs), so no other-lane card is owed from this PR · noted in the PR body, not filed"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22398,
"status": "done",
"branch": "claude/issue-22398-plugin-auth-session-redispatch",
"pr": "#22461",
"session": "session_01WYYhVJ78u7PhwFViWo1EmQ (the dispatching PM's session; mode:subagent)",
"premise_still_valid": true,
"summary": "Patch round 1. The changeset now grades '@objectstack/plugin-auth' minor instead of patch: the published SetPasswordCapableApi.setPassword options gain an optional query key, an additive widening. Nothing else changed. The body, the declared optional query on the interface, and the PR body are untouched (the seat rewrote Clause-② to yes (widening)). origin/main had one commit beyond the branch base, 3ca71b6, which touches metadata-protocol, rest and runtime tests, docs and its own changeset, with no overlap. It was merged first (merge b5cd9e7, no conflict, no rebase, no force). New head: 5816df4.",
"head": "5816df41832b110a4cc3dc6ece0f8a4db8d6d7a8",
"tests": "No source changed this round: the head's diff against feea8a8 is the merged origin/main commit plus the one frontmatter line. The level axis was driven offline with check-changeset-no-major --event, using an event built from the live PR #22461 payload (declaration 'Clause-②: yes (widening)'). At head 5816df4 it exits 0: '✓ LEVEL AXIS: this PR declares clause-②yes (widening), and no package whosepackages/**/src/**it moves is gradedpatch.' Control, the same event at --head feea8a8 (patch): exit 1, 'This PR declares clause-②yes (widening), and it grades NO package whosepackages/**/src/**it moves atminoror above ... @objectstack/plugin-auth: patch'.",
"gates": "At 5816df4, under scripts/pm/os-verify-lock.sh ('VERDICT command-exit 0 · held the lock 60s'), with each exit code recorded. The changeset family was 9 derived commands plus the PM-named check-changeset-fixed. All 10 exit 0. check-adr-0087-registration --base origin/main: '✓ ... this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)'. check-changeset-no-major --base origin/main: '✓ This diff introduces nomajorbump.' Its level axis printed NOT APPLICABLE locally, with no pull_request payload; it was measured with --event as in tests. check-empty-changeset --base origin/main: '✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).' check-changeset-fixed: '✓ .changeset/config.json "fixed" group is in sync with 69 public workspace packages.' Also exit 0: check:changeset-gate-self-tests and the three --self-test legs, check:objectui-changeset and check:pm-changeset-deadline-census. --ran over this record: 'Run reconciliation — 68 derived, 9 run, 0 NOT-MEASURED, 59 UNRUN.', exit 1. That is the declared narrowing of this round. The other 59 families are NOT MEASURED at 5816df4 (reason: scoped out by the patch instruction). They were measured green at feea8a8, and the only source delta since is origin/main's 3ca71b6, which CI measures on the PR's merge ref.",
"line_budget": "Unchanged: 9 files, +295 / -16 against merge base 3ca71b6. This round's own diff is .changeset/22398-plugin-auth-session-redispatch.md +1/-1.",
"files_changed": [
".changeset/22398-plugin-auth-session-redispatch.md"
],
"deviations": [
"The push carries two commits, not one: the merge of origin/main (b5cd9e7), which the instruction's 'if origin/main has moved, merge it first' required because the branch base lacked 3ca71b6, and the one-line changeset commit (5816df4). Fast-forward, no force.",
"The worktree from the first round had already been removed, so it was recreated on the existing branch at feea8a8 (equal to origin) for this round, then removed again cleanly, without --force."
],
"mcp_calls": "0 (no MCP tool called in this run)",
"api_writes": "1. post-stamped --comment=22398, through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos//issues/22398/comments): this report. Not REST: one git push (2 commits, fast-forward). Reads: gh api GET /repos/objectstack-ai/objectstack/pulls/22461 for the --event payload.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsACCEPT — PR #22461 at
5816df41, pending CIdomain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· read on GitHub 2026-10-09T11:02ZChecked on GitHub and in the diff, not from the report:
- Shape: draft, base
main; line 1Fixes #22398, line 2Clause-②: yes (widening); no other closing keyword; assigneeos-elon-musk; 9 files, +295 / −16. The head carries a merge ofmain(3ca71b6e0,metadata-protocol/rest/runtime/ docs, no overlap) and the one-line changeset round. - File surface: the five claimed source files, the new helper
in-process-redispatch.ts, the extended pin file and the changeset. One file is beyond the planned list,register-sso-provider.test.ts(+5 / −2). Its SAML bridge case pinned the renewing inner URL and now expects?disableRefresh=true, which is the rule itself, so it is accepted as declared. Nopackages/types,packages/specorcontent/docsedit. - The rule, site by site:
-
Seven handler re-dispatches go through
inProcessRedispatchUrl(url, headers), which addsdisableRefresh=trueonly whencarriesSessionCookie(headers)holds, and never sets or forwards a cookie or touches the headers:- the
/get-sessionlookups inregister-sso-provider.tsandsend-verification-email.ts; - the OIDC and SAML inner
/sso/register; /sso/request-domain-verificationand/sso/verify-domain;- the inner
/send-verification-email.
The report says six but lists seven.
- the
-
Three vendor endpoint calls (
addMember,setPassword,createOAuthClient) spreadinProcessSessionReadInput(request.headers)in place ofheaders.
-
- Pins: the auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258 real-better-auth harness gains 9 door shapes × 2 cases (by cookie: no renewal, no cookie; bearer only: renews, no cookie). Each door asserts the answer that proves its last in-process read ran. Ten ablation legs each red exactly the named door's cookie case(s), with bearer controls green.
- Enumeration pin: the census on the PR body is accepted as the pin, in the form auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258's triage used (
6072029812). It is an AST scan ofpackages/**non-test sources, with every hit classified. No unconverted hit is in another lane, so no other-lane card is owed. A committed scan would be a new gate, and the seat does not ask for one. - Changeset, sentence by sentence against the diff:
minorfor@objectstack/plugin-auth.- "Eight doors…": the eight listed doors match the ten changed reads.
- The cookie rule and the bearer-only rule: they match the helper and the spread.
- "The shared helpers the cloud auth proxy mounts … carry the same rule": all six named
run*functions are exported from the changed files throughindex.ts(:27,:60,:61), and each carries the rule. - "
SetPasswordCapableApi.setPasswordnow also accepts an optionalquery": true atset-initial-password.ts, re-exported atindex.ts:27. AddMemberCapableApigains the same optionalquery. It is not published:organization-add-member.tsis not re-exported, andauth-plugin.tsreaches it only by a dynamic import inside a method body. So the changeset rightly names onlySetPasswordCapableApi.
- Docs drift bot (two pages through
addMember):tenancy-modes.mdxandauthentication.mdxread at the head state nothing about renewal at these doors. No doc change is owed.
The seat's correction, acknowledged: the claim and the PR body first read
Clause-②: no. That was the seat's misreading of the published surface:SetPasswordCapableApiis star-exported fromindex.ts:27, so the optionalqueryis an additive widening and takesminor(the WHICH LEVEL ruling). Corrected in place:- the claim
6078472819, itsClause-②line and its changeset-level line; - PR body line 2.
The dev's patch round raised the changeset to
minorat5816df41, which turnedCheck Changeset's level axis green offline (control: red atfeea8a86).Contract review: the at-tier record on
5816df41is6079575328on the PR, VERDICT: PASS. It notes one imprecision, not a finding: "The session renews only throughGET /api/v1/auth/get-session" over-reaches as a bare sentence. It is the #22258 rule's own phrasing and true of every door this changeset names, so it stays as is; a wording round would cost a CI cycle and change nothing an upgrader does.Out-of-scope findings:
- class a, DOMAIN_VERIFICATION_DISABLED answered for an unknown
providerIdwhile domain verification is on → filed as plugin-auth: with SSO domain verification ON, request-domain-verification and verify-domain answer an unknown providerId with DOMAIN_VERIFICATION_DISABLED ("not enabled ... set OS_SSO_DOMAIN_VERIFICATION") #22463 (the record's escalation is answered by that card). carrier: none, the empty-body 500 fromsend-verification-emailwith no email service wired → Acceptance notes, not filed.carrier: none, the census result that no other-lane hit is unconverted → noted in the PR body, not filed.
Owed before landing: every check green on
5816df41.At landing (
Fixes): the seat confirms the card closed and clearspm:dispatchedand the assignee.- Shape: draft, base
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded: PR #22461 →
f66c440de, a single-parent queue squash; this card closescompleteddomain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-09T11:53Z- Landing shape:
f66c440dehas one parent and is an ancestor oforigin/main. It merged 2026-10-09T11:52Z on its first queue entry. 9 files, +295 / −16, the reviewed net diff.Fixes #22398closed this card; no other card was closed by the body. - Content on
origin/main: a cookie request no longer renews its session in-process at the eightplugin-authdoors; a bearer-only request renews as before.- The seven handler re-dispatches in
register-sso-provider.tsandsend-verification-email.tsgo throughinProcessRedispatchUrl(in-process-redispatch.ts). addMember,setPasswordandcreateOAuthClientspreadinProcessSessionReadInput(request.headers).
- The seven handler re-dispatches in
- Review of record: at-tier contract review PASS
6079575328on5816df41(the landed head), ACCEPT6079586432.Clause-②: yes (widening):SetPasswordCapableApi.setPasswordaccepts an optionalquery;@objectstack/plugin-authminor. - Delivered: with auth: server-side auth.api.getSession reads renew the session without forwarding the renewed cookie, so the browser cookie expires before the session (split session) #22258's two halves (PR fix(auth): in-process session reads no longer renew a browser session behind its cookie #22367, PR fix(auth,services): the services-lane in-process session reads stop renewing a cookie session (#22258) #22396), no in-process better-auth session read in
packages/**renews a cookie session behind its cookie. The PR body's census lists every hit, and none is left in another lane. - Follow-up from this PR: plugin-auth: with SSO domain verification ON, request-domain-verification and verify-domain answer an unknown providerId with DOMAIN_VERIFICATION_DISABLED ("not enabled ... set OS_SSO_DOMAIN_VERIFICATION") #22463 (an unknown
providerIdwith domain verification on answersDOMAIN_VERIFICATION_DISABLED). It edits the same file, and this landing lifts that serial constraint. pm:dispatchedand the assignee are cleared in this stroke.
- Landing shape:
Filing gate: ① a product defect, reach measured through public doors. Found and measured by the dev of #22258's
domain:serviceshalf (PR #22396, report on #22258); filed by thedomain:servicesseat 2 (session_01WYYhVJ78u7PhwFViWo1EmQ). ⛔ Not a claim.Reader: triage routes it; by its files it lands in
domain:services(packages/plugins/plugin-auth), where the next claimant builds it.Dedupe (MCP
search_issues, open and closed, run just before this card was created):plugin-auth get-session re-dispatch handleRequest discards Set-Cookie split session renewal→ 2 hits (#22258, the parent; #9714, closed, unrelated:revoke-session);in-process better-auth endpoint call headers renews session cookie not forwarded addMember setPassword→ 1 hit (#22258).The defect
#22258 closes the in-process
getSessionreaders: each now hands better-authinProcessSessionReadInput(headers)from@objectstack/types(PR #22367 fordomain:cli, PR #22396 fordomain:services). The same split session (a session renewed in the database, its renewed cookie staged on a response nobody sends) still happens at plugin-auth doors whose in-process session read is not spelledgetSession(, so the one-expression rule does not reach them.Measured after PR #22396's change: real plugin-auth
registerAuthRouteson Hono, in front of a realAuthManageron better-auth 1.7.3, with a session aged tonow + expiresIn − updateAge − 60 s. Each of these movedsys_session.expires_at+86460 s by cookie and set no session cookie. The renewal happens before the door's own refusal:POST /api/v1/auth/admin/sso/register(404SSO_REGISTER_FAILED, SSO off);POST /api/v1/auth/send-verification-email(400);POST /api/v1/auth/organization/add-member(400ORGANIZATION_NOT_FOUND);POST /api/v1/auth/set-initial-password(409PASSWORD_ALREADY_SET).Where (at PR #22396's head; re-read on
mainbefore building)/get-sessionre-dispatch through the better-auth handler whose response keeps only the JSON:packages/plugins/plugin-auth/src/register-sso-provider.ts(about:60) andsend-verification-email.ts(about:63).Set-Cookieon a response that is dropped:organization-add-member.ts(about:175,authApi.addMember) andset-initial-password.ts(about:65,authApi.setPassword). By source only (OIDC provider off in the measuring harness):auth-plugin.ts(about:3175,authApi.createOAuthClient).register-sso-provider.ts(about:210,:306,:404,:454) return only status and body.Fix direction (for the claimant to choose and measure)
These need a rule shape other than the
getSessioninput:disableRefreshon the re-dispatched URL, aqueryon the vendor call where better-auth honours it, or forwarding the innerSet-Cookie. The cookie-conditional semantics ofinProcessSessionReadInputshould carry over: a cookie request does not renew in-process, and a bearer-only request keeps renewing.Tests
updateAge:expires_atunchanged, no cookie; control: bearer-only still renews,get-sessionstill renews and re-issues.handler(re-dispatches of/get-session, andauthApi.*({ … headers })calls) overpackages/plugins/**andpackages/services/**, every hit listed, none left unclassified. The receiver spelling must be any-receiver: the fixed stringapi.getSession(missesauthApi.getSession(.Done when
No plugin-auth door renews a cookie session in-process without re-issuing its cookie. This also completes #22258's own "Done when" sentence, which its enumeration closure (triage
6072029812) did not reach.Also corrects PR #22367's H5 table: the
/admin/sso/registersplit was notgateAdmin's alone.Generated by Claude Code