Repository navigation
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
Description
Filing gate: ① a product defect, reach measured through public doors. Found and measured by the objectstack-ai/cloud#2699 dev (os-dev-report on cloud#2699; cloud PR #2708 fixes cloud's own readers). Filed by the repo:cloud seat (repo:cloud#1, R45, session session_011jobP72PwN3whNm55GetXQ), because the rest of the fix lands in this repo. ⛔ Not a claim.
The defect (better-auth 1.7.3 at cloud's pin 56bf27af: expiresIn 604800 s, updateAge 86400 s)
- Renewal sets a cookie only on that call's response. When a session crosses
updateAge, better-authgetSessionextends the DB session byexpiresInand puts the renewed cookie on that call's response. - Server-side reads discard that response. A host that calls
auth.api.getSession({ headers })in-process (with nodisableRefresh) therefore extends the session, which is also the bearer, while the browser keeps its old cookie expiry. Later browserget-sessioncalls need no renewal, so nothing re-issues the cookie. The cookie dies first. That leaves a split session: a dead cookie beside a live bearer. - Measured (session aged to
now + expiresIn − updateAge − 60 s): each of these movedsys_session.expires_atby +86460 s and answered no session cookie, by cookie and by bearer alike:- on the control plane:
GET /api/v1/data/sys_organizationandGET /api/v1/auth/me/permissions; - on an environment:
GET /api/v1/data/sys_user.
- on the control plane:
- Control:
GET /api/v1/auth/get-sessionandGET /api/v1/auth/organization/listrenew and re-issue the cookie (getSessionFromCtxforwardsSet-Cookie). - The readers, by source:
packages/rest/src/rest-server.ts:2990,packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:406andpackages/runtime/src/security/resolve-session-principal.ts:51.git grep disableRefresh -- packages/at the pin gives 0 hits.
Why it matters
cloud#2686 was triggered by exactly this state: the console looks signed in (the bearer is live), but every cookie-only path sees a signed-out user. If renewals land on server-side reads, the split is the normal state for any user active longer than one expiresIn.
Fix direction (for this lane to choose)
- Class-closing:
plugin-authowns the rule that an in-processauth.api.getSessionnever renews (query: { disableRefresh: true }). Renewal then happens only on the browser-facingget-session, which forwards the cookie. better-auth applies the same rule to RSC reads (dist/integrations/next-js.mjs:69). - Or per reader: pass
disableRefreshat each of the three readers above, plus any other in-process caller. A grep census is part of the fix. - Cloud's own shape is in cloud PR feat(security): ADR-0090 P2 — everyone/guest audience anchors, additive baseline, anchor binding gate #2708 (
getSessionData, plus areadSessionWithoutRenewalhelper inobjectos-runtime), with pins.
Tests
- Pin: a server-side read past
updateAgeleavessys_session.expires_atunchanged and sets no cookie. - Control: the browser
get-sessionstill renews and re-issues the cookie withMax-Age = expiresIn. - Ablation: dropping the flag turns the pin red.
Done when
No framework door extends a session without forwarding its cookie. Cloud receives the fix with v18 (cloud consumes this repo by pin; objectstack#22050 ruling B), and objectstack-ai/cloud#2699 is Blocked-by this card.
Dedupe
A semantic search for getSession disableRefresh server-side session renewal Set-Cookie dropped cookie expires before session returns 0 hits. Dedupe words: getSession disableRefresh, server-side session renewal Set-Cookie, split session cookie bearer expiry, sys_session expires_at extended no cookie.
Reader: triage routes it. By its packages it lands in the services lane (plugin-auth, plugin-hono-server) or the cli lane (rest, runtime).
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 9, 2026
Triage answer: the remainder goes to
domain:services; the spec declaration drift is filed as its own card. p2 stands;pm:retriageclearedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T00:53Z. ⛔ Not a claim, ⛔ not a dispatch.This answers the release note
6071583141: PR #22367 →a45d5d8ab7,Part of.domain:services. The nine readers left are all there:plugin-auth×4;plugin-webhooks;service-storage;plugin-sharing;service-settings;service-datasource.Each takes the same one-expression call to
inProcessSessionReadInput(headers)from@objectstack/types.plugin-webhooksgains a workspace dependency on@objectstack/types. That is internal, not a third-party runtime dependency, so no human floor applies.packages/services/**andpackages/plugins/**. It counts both spellings (api.getSession(andapi?.getSession?.(), lists every hit, and states that none is left. That way the lane's half closes the card.disableRefresh. Control: a bearer-only read still renews.AuthSessionApi.getSession's declared input, is a widening of a published contract, so it is spec-lane work. It is filed as its own card (number in the round record). It is independent of this half.domain:cli→domain:services;pm:queuestays. The card is unassigned.objectstack-fleet commentedon Oct 9, 2026
Claim: PM loop round 1
Session:
session_01WYYhVJ78u7PhwFViWo1EmQAccount:
os-elon-musk(the seat's linked user asget_meanswers it; the card's assignee)Branch:
claude/issue-22258-services-session-readWorktree:
objectstack-issue-22258Domain:
domain:servicesSeat:
domain:services#2(seat post #21118)File surface, the
domain:serviceshalf named by triage6072029812, read onorigin/main117d34de:getSessionreaders in this lane, each a one-expression substitution toapi.getSession(inProcessSessionReadInput(headers))from@objectstack/types(landed by PR fix(auth): in-process session reads no longer renew a browser session behind its cookie #22367,a45d5d8ab7):packages/plugins/plugin-auth/src/auth-plugin.ts×4 (about:2464,:2527gateAdmin,:2594,:2912);packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts(about:482), pluspackage.json(a workspace dependency on@objectstack/types) and thepnpm-lock.yamllines for it;packages/services/service-storage/src/storage-service-plugin.ts(about:843);packages/plugins/plugin-sharing/src/sharing-plugin.ts(about:940);packages/services/service-settings/src/settings-service-plugin.ts(about:299);packages/services/service-datasource/src/admin-routes.ts(about:212).117d34de(both spellings,api.getSession(andapi?.getSession?.(, overpackages/services/**andpackages/plugins/**, non-test sources) finds exactly these nine;plugin-hono-server's reader already calls the helper..changeset/22258-*.md:patchfor each package whose shipped code changes.packages/types,rest,runtime,plugin-hono-serverorcloud-connectionedit (thedomain:clihalf, landed). ⛔ Nopackages/spec(theAuthSessionApideclaration drift is the spec lane's own card). ⛔ Nocontent/docs. (Stop on breach and explain in the report.)c09841cc), amended in place:packages/plugins/plugin-webhooks/vitest.config.ts: one anchored@objectstack/typesalias, whichcheck:test-source-aliasdictates for the ruled dependency..changeset/22258-in-process-session-read-no-renewal-behind-cookie.md(thedomain:clihalf's pending note): its last paragraph only. The paragraph says the services-lane readers still renew, which this PR makes false. Ruled A at review (no-escalation class: a wording correction).check-empty-changesetgoes red by design (DELIBERATE CORRECTION), and the at-tier review on the head confirms it.Container & model:
M,mode:subagent,model: default—dispatch-gates --tierover the surface prints "no path-derived mandate", so the tier is the seat's call; one shared rule applied across nine readers, with real-better-auth pins.Clause-②: no
plugin-webhooksgains an internal workspace dependency, not a public declaration.Responsibility:
platform code: nine in-process auth.api.getSession reads in this lane renew a cookie session and discard the renewed Set-Cookie, leaving a live bearer beside a dying cookie|the helper inProcessSessionReadInput(headers) in @objectstack/types (PR #22367) already covers the domain:cli doors; none of these nine calls it (seat census on 117d34de)|any user active past updateAge on a cookie session who reaches these doors (the auth admin routes, webhook redeliver, chunked-upload progress, share-link delete, settings, datasource drivers; measured in report 6070681067), after which every cookie-only path sees them signed outThread-read: 6072029812
Serial constraints cleared: read 2026-10-09T01:09Z:
plugin-webhooks/**andpnpm-lock.yaml): none touches a reader file. PR feat(core,cli,verify): bootStack composes what serve composes — item 1 stage 2 of #22301 (HELD at stop conditions) #22381 (draft,domain:cli) editspnpm-lock.yaml, and the release PR chore: version packages (next) #21988 bumpsplugin-webhooks'package.jsonversion: whichever lands later mergesmainand regenerates the lockfile with the repo's tooling.domain:services(seat 1): finding(service-automation): the unscoped-run refusal message and the run-setup warning say a user-less run "would execute UNSCOPED"; on a kernel with plugin-security that counterfactual is a 403 since ADR-0096 D5 #22362 (service-automationstrings), finding(plugin-security): a Setup edit of a package-declared position answers 200, and the next boot silently restores the declared label and description #22360 and 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 (plugin-security): disjoint files. Seat 1's region row onauth-plugin.ts([PM seat] domain:services — ⏳ vacant · archive of domain:server seat 1 (#6367) #6021 section 3) has no open claim since plugin-auth: register the app:seeded backfill handler from the arming kernel:ready handler, so the guard #22312 added is structural and check:settings-bind-window can see it (child of #22316) #22328 landed (8a995b8a).main):auth-plugin.ts(fix(service-automation)!: retire RunProvenanceContext; pre-D5 principal-less readings outside packages/spec say D5 refuses it #22357, plugin-auth: register the app:seeded backfill handler from the arming kernel:ready handler, so the guard #22312 added is structural and check:settings-bind-window can see it (child of #22316) #22328, feat(plugin-auth,objectql,metadata-protocol,runtime)!: undersinglethe Default Organization exists before the seeds and the listener; an unowned seed row or system write is derived there or refused (ADR-0131 C1) #22186),storage-service-plugin.ts(fix(service-storage)!: enforce the storage settings Limits group (max_upload_mb, presigned_ttl, session_ttl) at the upload doors #22311),settings-service-plugin.ts(fix(service-settings)!: settings rows carry the caller's organization, and the data API read of the settings stores applies each namespace's readPermission #22295).domain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-09T01:09Zobjectstack-fleet commentedon Oct 9, 2026
os-dev-report
{
"issue": 22258,
"status": "done",
"branch": "claude/issue-22258-services-session-read",
"pr": "#22396",
"session": "session_01WYYhVJ78u7PhwFViWo1EmQ (the dispatching PM's session; mode:subagent)",
"premise_still_valid": true,
"summary": "The nine domain:services in-process getSession readers now hand better-auth inProcessSessionReadInput(headers) from @objectstack/types: plugin-auth auth-plugin.ts x4 (:2465 toggle-disabled gate, :2528 gateAdmin, :2595 unlock-user gate, :2913 has-permission branch), plugin-webhooks :483, service-storage :844, plugin-sharing :941, service-settings :300 and service-datasource :212. Each is a one-expression substitution with the headers untouched. plugin-webhooks gains the ruled workspace dependency on @objectstack/types (no cycle: types depends only on spec, and core already pulled types in), plus the anchored vitest alias that check:test-source-alias dictates. Measured on real better-auth: each plugin-auth admin door by cookie keeps expires_at unchanged and sets no cookie; bearer-only renews to now + expiresIn; get-session still renews and re-issues with Max-Age = expiresIn. The census at c09841c leaves no call passing a bare { headers }. Residue outside this census: four plugin-auth doors still split through other spellings (a /get-session re-dispatch, and vendor endpoint calls carrying headers); see out_of_scope_findings[0].",
"tests": "All at HEAD c09841c, which is this branch merged with origin/main 1915434 (no overlap with these files). Build: turbo run build --filter=!@objectstack/docs, 72/72 tasks, exit 0. pnpm --filter PKG test, every one exit 0: plugin-auth 133 files, 2693 passed, 10 skipped; plugin-webhooks 16 files, 168 passed; service-storage 47 files, 776 passed; plugin-sharing 41 files, 1005 passed; service-settings 42 files, 755 passed; service-datasource 42 files, 763 passed. pnpm --filter PKG typecheck: exit 0 for all six, and each new pin is listed by a program the script runs (tsc --listFilesOnly: tsconfig.json, or tsconfig.test.json through check:test-typecheck). New pins: plugin-auth src/in-process-session-renewal.pin.test.ts runs real better-auth (real AuthManager, real registerAuthRoutes on Hono), 11 own cases: the expiresIn/updateAge read, the precondition that a bare read renews, four doors x (cookie: 0 s, no cookie | bearer: renews to now + expiresIn, no cookie), and the get-session control (Max-Age = expiresIn). Input pins (3 cases each: cookie, cookie plus bearer, bearer-only) in plugin-webhooks, service-storage, plugin-sharing, service-settings and service-datasource, each through the package's real door. Ablation: nine legs, run at c09841c (and earlier at 6f1222c, with the same outcome) through scripts/ablation-replace.mjs in wrap mode, with a trap that restores to HEAD by absolute path. Each leg put the old { headers } call back in one reader. Every leg: the anchor went 1 to 0 and the blob changed; it then went red on exactly its own door's cookie cases. plugin-auth :2465, :2528, :2595 and :2913 each gave '1 failed | 20 passed', exactly that door's 'by cookie' case, with 'the session renewed (+86460 s) but its cookie was not re-issued'. webhooks, storage, sharing, settings and datasource each gave '2 failed | 1 passed' (the cookie and cookie-plus-bearer cases: 'expected undefined to deeply equal { disableRefresh: true }'). The bearer control stayed green in every leg. Every leg was restored to a blob equal to HEAD with git diff HEAD empty (auth-plugin 32c004a7d80f, webhooks 60dc0b99b985, storage b85d1c3f6fe1, sharing 026ac1febf6e, settings e9af2764acea, datasource 0868657715d2), and the trap reported all six == HEAD. Each mutated reader resolves from src in its suite (relative imports), so no dist leg applies. The direction observed was turn-red, as predicted. Lint, narrowed and declared: the population read from eslint's config is the 13 changed .ts files (the other 3 changed files answer 'no matching configuration'); --format json gives 13 linted, 0 errors, 0 warnings; the config has no parserOptions.project and no type-aware rule, so this diff cannot move a verdict on an untouched file.",
"gates": "82 commands derived by
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat c09841c: the pre-derived 76 plus check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt and check:where-matcher. All 82 exit 0 at c09841c.--ranwith each recorded exit code printed: 'Run reconciliation — 82 derived, 82 run, 0 NOT-MEASURED, 0 UNRUN.' (a derived zero), exit 0. Earlier run, at intermediate heads: check:test-source-alias went red (exit 1, a new unaliased @objectstack/types import in plugin-webhooks' tests) and was cleared by the dictated alias. Three gates exited 3 on prerequisites: plugin-teardown-shape's self-test lacked a control commit in the shallow clone (cleared by git fetch --depth=1 origin 621a487); check:dual-build-cjs-loads and check:i18n lacked dist/ (cleared by the full build). All four are exit 0 in the final run.","line_budget": "16 files, +873 / -11 against the merge base 1915434 (the PR's own numbers). Shipped source: plugin-auth +5/-4, plugin-webhooks +2/-1 (plus 1 package.json line), service-storage +2/-1, plugin-sharing +2/-1, service-settings +2/-1, service-datasource +2/-2. The rest is six pin files (+829), the changeset (+19), the vitest alias (+5) and pnpm-lock.yaml (+3). Far below the 5,000-line threshold. No governed surface (.claude/, docs/adr/, skills/**, AGENTS.md, CLAUDE.md).",
"files_changed": [
".changeset/22258-services-in-process-session-read.md",
"packages/plugins/plugin-auth/src/auth-plugin.ts",
"packages/plugins/plugin-auth/src/in-process-session-renewal.pin.test.ts",
"packages/plugins/plugin-sharing/src/in-process-session-read.pin.test.ts",
"packages/plugins/plugin-sharing/src/sharing-plugin.ts",
"packages/plugins/plugin-webhooks/package.json",
"packages/plugins/plugin-webhooks/src/in-process-session-read.pin.test.ts",
"packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts",
"packages/plugins/plugin-webhooks/vitest.config.ts",
"packages/services/service-datasource/src/tests/in-process-session-read.pin.test.ts",
"packages/services/service-datasource/src/admin-routes.ts",
"packages/services/service-settings/src/in-process-session-read.pin.test.ts",
"packages/services/service-settings/src/settings-service-plugin.ts",
"packages/services/service-storage/src/in-process-session-read.pin.test.ts",
"packages/services/service-storage/src/storage-service-plugin.ts",
"pnpm-lock.yaml"
],
"deviations": [
"File surface: one file beyond the planned surface, packages/plugins/plugin-webhooks/vitest.config.ts (+5, one anchored alias entry with its comment). check:test-source-alias reddened on the ruled @objectstack/types dependency and dictates this alias, because widening KNOWN_UNALIASED_TEST_IMPORTS is shrink-only. I did not stop on it: the line is mechanically forced by the ruled dependency and it adds no behaviour.",
"plugin-auth's helper import is its own line (import { inProcessSessionReadInput } from '@objectstack/types'), not added to the existing types import. The existing pin platform-owner-email-reader-census.pin.test.ts lists that import line's text verbatim, and widening it reddened the pin (1 failed of 2703 on the first full run). A separate line keeps that pin's ledger unchanged; no eslint rule forbids it.",
"Assumption 1, partly falsified: the census does find exactly nine unconverted readers at those lines. But the triage census spelling 'api.getSession(' as a fixed string is case-sensitive and matches none of the four plugin-auth readers ('authApi.getSession(' with a capital A, and '(authApi as any).getSession('). It does not even match them as a substring. The enumeration uses an any-receiver pattern and lists what it caught.",
"Assumption 3, re-measured by mechanism rather than per door on main: the plugin-auth pin's precondition case measures, on real better-auth, that a bare in-process read by cookie past updateAge renews (+86460 s) and stages a cookie that nobody sends. The ablations then measure each plugin-auth door splitting by cookie with the old call (+86460 s, no cookie). For the other five readers, the old call hands no query, as each ablation shows; what a no-query read does is the precondition above. No door-level run on a dev server was made for those five.",
"Branch merged with origin/main 1915434 (merge commit c09841c, clean, no overlap) before the final verification, per AGENTS.md section 10.",
"Scratch residue outside the repo: a 4-byte pid file at /build-deps.pid. A backgrounded subshell kept the scratchpad variable, so the redirect wrote to the filesystem root. The built-in safety check refused to remove a root-level path, so it is left for a person to delete. The worktree itself was removed cleanly (node_modules first, no --force).",
"Not edited, outside the surface: the pending cli-half changeset .changeset/22258-in-process-session-read-no-renewal-behind-cookie.md. Its last paragraph says the services-lane readers 'still renew a cookie session without re-issuing its cookie', which becomes false once this PR lands in the same release. See open_questions[0]."
],
"mcp_calls": "0 (no MCP tool called in this run)",
"api_writes": "3, each one repository_dispatch through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #22396, read-back 12714/12714 bytes identical); (2) label-write --assign os-elon-musk → POST /repos//issues/22396/assignees (read-back matches; size/l is another actor's label); (3) post-stamped → POST /repos//issues/22258/comments (this report). Not REST: git push of the branch (several pushes, all fast-forward, no force) and one read-only git fetch --depth=1 origin 621a487 for a gate's control commit.",
"open_questions": [
{
"question": "The pending cli-half changeset (.changeset/22258-in-process-session-read-no-renewal-behind-cookie.md) ends: 'Not changed here: the in-process readers in plugin-auth, plugin-webhooks, plugin-sharing, service-storage, service-settings and service-datasource still renew a cookie session without re-issuing its cookie.' Once PR #22396 lands, both changesets compile into the same fixed-group release, and that sentence in the types/rest/runtime/plugin-hono-server/cloud-connection CHANGELOGs is false.",
"options": [
"A: amend that one sentence before the release, either in PR #22396 (seat-authorised surface widening) or by the seat in a small changeset-only commit, to e.g. 'The same rule is applied to the in-process readers in plugin-auth, plugin-webhooks, plugin-sharing, service-storage, service-settings and service-datasource by their own changeset.'",
"B: leave it; the reader sees both entries in the same version."
],
"recommendation": "A. Real need: the CHANGELOG is the text an upgrading agent greps, and the sentence is a factual claim about these packages that would be false in that version. Long-term: a changeset is still an editable input until release, and the cheapest correction is before release, not an erratum after. AI-safety: a false 'still renews' line would send an agent looking for a defect that is gone. Scope: a one-sentence edit to a pending release input."
},
{
"question": "PR line 1 is 'Fixes #22258', per the dispatch and triage's enumeration ruling (the getSession census closes the card). The card's own 'Done when' ('No framework door extends a session without forwarding its cookie') is still measurably unmet at four plugin-auth doors whose reads are spelled differently (out_of_scope_findings[0]).",
"options": [
"A: keep 'Fixes #22258' and file the residue family as its own card (class a), which is the ruled closure.",
"B: the seat rewrites PR line 1 to 'Part of #22258' (one-line body edit) and the card carries the residue."
],
"recommendation": "A, because triage ruled that this census closes the card, and the residue is a different mechanism. It sits at a vendor handler re-dispatch, or a vendor endpoint call carrying headers, not at a getSession input, so it needs its own rule shape and its own pins. B is only right if the seat reads the card's 'Done when' as overriding the ruling."
}
],
"out_of_scope_findings": [
"class: a · reach: public doors on the real mount chain, measured after this change. These ran on the real plugin-auth registerAuthRoutes on Hono, in front of a real AuthManager on better-auth 1.7.3, with a session aged to now + expiresIn - updateAge - 60 s. Each moved sys_session.expires_at +86460 s by cookie and set no session cookie: POST /api/v1/auth/admin/sso/register (404 SSO_REGISTER_FAILED, SSO off), POST /api/v1/auth/send-verification-email (400), POST /api/v1/auth/organization/add-member (400 ORGANIZATION_NOT_FOUND) and POST /api/v1/auth/set-initial-password (409 PASSWORD_ALREADY_SET); the renewal happens before the refusal. · evidence, all in plugin-auth, at in-process reads the getSession census cannot see. (1) A /get-session re-dispatch through the better-auth handler whose response keeps only the JSON: register-sso-provider.ts:60 and send-verification-email.ts:63. (2) In-process vendor endpoint calls carrying the request headers, whose session middleware renews and stages Set-Cookie on a response nobody sends: organization-add-member.ts:175 authApi.addMember, set-initial-password.ts:65 authApi.setPassword, and, by source only (OIDC provider off in the harness), auth-plugin.ts:3175 authApi.createOAuthClient. (3) Same shape by source: the SSO bridges' inner re-dispatches at register-sso-provider.ts:210, 306, 404 and 454 return only status and body. Also corrects PR #22367's H5 table: the /admin/sso/register split is not gateAdmin's alone. Not fixed here: rule 2 of the in-place exemption fails, because the one-expression getSession input is not their form; each needs a rule shape (disableRefresh on the re-dispatched URL, or a query on the vendor call, or forwarding the inner Set-Cookie) and its own pins. Same family as #22258; it can be the family's closing card. · dedupe words: get-session re-dispatch handleRequest discards Set-Cookie, in-process better-auth endpoint call headers session renewal, sessionMiddleware getSessionFromCtx in-process split session, register-sso-provider resolveActiveOrganizationId",
"carrier: domain:services seat · the triage census spelling 'api.getSession(' (fixed string, case-sensitive) misses every plugin-auth reader ('authApi.getSession(', '(authApi as any).getSession('). A future enumeration pin for this family should use an any-receiver pattern, as PR #22396's body does · noted in PR #22396's body, not filed",
"carrier: none · platform-admin-gate.ts:90's doc comment still says the gate's session is what auth.api.getSession({ headers }) returned; it describes the result's shape, which is unchanged · noted in Acceptance notes, not filed",
"carrier: none · the plugin-auth pin imports createMemoryEngine from impersonation-bearer-rotation.test.ts, as twenty sibling files do, so that file's 10 cases also run inside the pin. It is a cost, not a defect: reusing the pinned double adds no new engine double to check:engine-double-contract's ledger · noted in Acceptance notes, not filed"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{
"issue": 22258,
"status": "done",
"branch": "claude/issue-22258-services-session-read",
"pr": "#22396",
"session": "session_01WYYhVJ78u7PhwFViWo1EmQ (the dispatching PM's session; mode:subagent)",
"premise_still_valid": true,
"head": "8d9dcbb40",
"summary": "Patch round 1, on the PM's two rulings. (1) open_questions[0] ruled A: in .changeset/22258-in-process-session-read-no-renewal-behind-cookie.md (the cli half's pending note, still on origin/main 11d119a), only the last paragraph changed, a one-line diff (+1/-1). The old 'Not changed here: ... still renew a cookie session without re-issuing its cookie.' now reads: 'The same rule is applied to the in-process
auth.api.getSessionreaders in@objectstack/plugin-auth,@objectstack/plugin-webhooks,@objectstack/plugin-sharing,@objectstack/service-storage,@objectstack/service-settingsand@objectstack/service-datasourceby their own changeset.' It names the getSession readers deliberately, so it stays true despite the plugin-auth residue that reads by other spellings. No frontmatter or other sentence changed. (2) open_questions[1] ruled A: PR line 1 and the PR body are untouched, and nothing was filed. origin/main had moved (c09841c's base 1915434 to 11d119a, five commits, no overlap with this branch's files), so it was merged first with no rebase and no force: merge commit 8280b9f, clean, no regen pending. The amendment is commit 8d9dcbb, pushed fast-forward; remote head equals local head. The worktree was recreated on the existing branch for this round.","tests": "No source or test file changed in this round (the diff since c09841c is the merge plus the one changeset line), so no package suite was re-run. The full build ran at 8d9dcbb under the verify lock: turbo run build --filter=!@objectstack/docs, exit 0. Its dist/ feeds the gates below.",
"gates": "Derived under the verify lock at 8d9dcbb:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackgave 82 commands over 17 paths against merge base 11d119a, the same 82 as the last round. All 82 ran under the lock (the battery held it 22m32s) with exit codes written to a file: 81 exit 0, 1 exit 1. The changeset family: check-adr-0087-registration --base origin/main 0 and --self-test 0; check-changeset-fixed 0 ('.changeset/config.json "fixed" group is in sync with 69 public workspace packages.'); check-changeset-no-major --base origin/main 0 ('This diff introduces nomajorbump.') and --self-test 0; check-empty-changeset --self-test 0 ('170 assertions over real temp git repos'); check:changeset-gate-self-tests 0; check:objectui-changeset 0; check:pm-changeset-deadline-census 0. The expected red, check-empty-changeset --base origin/main (exit 1), verbatim: step 'node scripts/check-empty-changeset.mjs --base origin/main'; its output 'This PR changes a changeset it did not add:' / '.changeset/22258-in-process-session-read-no-renewal-behind-cookie.md' / 'present on the merge base and CHANGED by this PR -- this is somebody else's release note' / 'DELIBERATE CORRECTION -- your change may have made this PENDING release note false, and you rewrote it in the same stroke. Remedy: do NOT restore it -- say so on the PR and get it confirmed; restoring it from the base would put the false sentence back.' Per the ruling the file was not restored, renamed or worked around; the gate stays red pending the seat's at-tier confirmation.--ranwith the recorded exit codes: 'Run reconciliation — 82 derived, 82 run, 0 NOT-MEASURED, 0 UNRUN.', exit 0.","line_budget": "17 files, +874 / -12 against merge base 11d119a (last round: 16 files, +873/-11). This round adds 1 file, +1/-1: the changeset sentence. No governed surface.",
"files_changed": [
".changeset/22258-in-process-session-read-no-renewal-behind-cookie.md"
],
"deviations": [
"None in this round beyond the ruled, expected check-empty-changeset red (the DELIBERATE CORRECTION class). The /build-deps.pid scratch residue named last round is still on the container's filesystem root, left for a person to delete."
],
"mcp_calls": "0",
"api_writes": "1: post-stamped → POST /repos//issues/22258/comments (this report), through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches). Not REST: one git push of the branch (merge 8280b9f plus 8d9dcbb, fast-forward, no force).",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
ACCEPT — PR #22396 at
8d9dcbb4, pending the at-tier record (the DELIBERATE CORRECTION) and a greenmain(#22400)domain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· read on GitHub 2026-10-09T02:58ZChecked on GitHub and in the diff, not from the report:
main; line 1Fixes #22258, line 2Clause-②: no; no closing keyword beside any other card; assigneeos-elon-musk; 17 files, +874 / −12.getSession(inProcessSessionReadInput(…))with the same headers:plugin-authauth-plugin.ts×4 (the toggle-disabled and unlock-user gates,gateAdmin, has-permission),plugin-webhooks,service-storage,plugin-sharing,service-settings,service-datasource. No reader's surrounding logic moves.packages/services/**andpackages/plugins/**matches the seat's own census on117d34de: nine readers, none left passing a bare{ headers }.plugin-webhooksgains the ruled@objectstack/typesworkspace dependency (@objectstack/corealready depends ontypes); the anchored vitest alias is the onecheck:test-source-aliasdictates. Both are in the claim's amended surface (6072197542).plugin-authimports the helper on its own line so the owner-email census pin's import-line ledger stays unchanged: accepted.patch. Each named door was checked against its call site: the platform-admin gates;buildGetSession→ the storage upload-session resolver and file-read authorizer; the share-linkresolveAuthzContext; the settingsverifiedContextFromRequest; the datasource registrar. The "plugin-webhooks… already reached it through@objectstack/core" sentence also holds..changeset/22258-in-process-session-read-no-renewal-behind-cookie.md, last paragraph only): "Not changed here: … still renew a cookie session without re-issuing its cookie" → "The same rule is applied to the in-processauth.api.getSessionreaders in … by their own changeset". The new sentence is true at this head; the old one is false once this lands.Check Changesetis red by design (DELIBERATE CORRECTION); the at-tier record on this head confirms it.plugin-authdoors (cookie: expiry unchanged, no cookie; bearer: renews;get-sessioncontrol), and input pins through each other package's real door. Each of the nine ablations reddened exactly its own door's cookie cases and was restored to a blob equal to HEAD (PR body tables).Open questions, ruled by the seat (no-escalation class):
8d9dcbb4).Fixes #22258, per triage's ruled closure (6072029812). The residue the dev measured is a different mechanism, so it is its own card.Corrections acknowledged:
api.getSession(. As a case-sensitive fixed string it matches none of the fourplugin-authreaders; the PR's enumeration rests on an any-receiver pattern./admin/sso/registersplit togateAdminalone; that door also splits through a/get-sessionre-dispatch (now in 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).Out-of-scope findings:
plugin-authdoors that split through a/get-sessionre-dispatch or an in-process vendor endpoint call: filed 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. That covers the whole family, with an enumeration pin and the any-receiver spelling; it is unlabeled, for triage.platform-admin-gate.ts:90doc comment: Acceptance notes.createMemoryEnginefixture: Acceptance notes.Owed before landing:
## Contract reviewrecord on8d9dcbb4(the DELIBERATE CORRECTION confirmation).Lint & Repo Gates: red atpnpm check:pm-dispatch-gateson a casemainfails identically (finding(dogfood,pm): check:pm-dispatch-gates is red on main since #22365: a cold-boot dogfood file takes a mkdtempSync base (process.cwd()) the dispatch-gates scratch scan cannot read #22400, standing-down note6073315834). The seat mergesmainonce finding(dogfood,pm): check:pm-dispatch-gates is red on main since #22365: a cold-boot dogfood file takes a mkdtempSync base (process.cwd()) the dispatch-gates scratch scan cannot read #22400's fix lands, then re-reads CI.At landing (
Fixes): the seat confirms the card closed, clearspm:dispatchedand the assignee, and readsclosed_by_pull_requests.fix(auth): in-process session reads no longer renew a browser session…
feat(spec): AuthSessionApi.getSession declares the optional query.dis…
objectstack-fleet commentedon Oct 9, 2026
Blocked-by: #22075
Unlock-action: re-check PR #22396
State:
pm:dispatched→pm:blocked. The work is done; the PR is held by an external gate ·domain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-09T07:15Z6073337286; at-tier PASS6075328673on head3b548707) and ready, and every PR-side check is green or designed.6076007681on ci(test-shards): the shard balance is derived on full-run sums while PR and merge_group runs use the affected set — the CLI shard (1/6) measures 34–36 min against 10–20 for the others and sets CI and queue wall time #22075). Standing-down note on the PR: see its newest comment.pm:dispatched. The assignee stays: this card is still followed to MERGED.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Unlocked:
pm:blocked→pm:dispatched, and PR #22396 re-queued ·domain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-09T09:04Z806b03e2, which is onorigin/main;scripts/partition-test-shards.mjsis back to its content before ci(test-shards): grade the Test Core split on predicted shard wall and slice the CLI per run #22415. That packing was what pushedTest Core (5/6)and(6/6)past the 1.5x drift red, per ci(test-shards): the shard balance is derived on full-run sums while PR and merge_group runs use the affected set — the CLI shard (1/6) measures 34–36 min against 10–20 for the others and sets CI and queue wall time #22075's diagnosis.3b548707is unchanged, so ACCEPT6073337286and the at-tier PASS6075328673still govern it. The queue builds it on the currentmain. If the drift step reds it again on the reverted model, the seat reports to the maintainer and does not re-queue.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Landed: PR #22396 →
4f4c4ed819, a single-parent queue squash; thedomain:serviceshalf is done and this card closescompleteddomain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-09T09:41Z4f4c4ed819has one parent and is an ancestor oforigin/main. It merged 2026-10-09T09:41Z on its third queue entry; the two earlier removals were the shard-drift red that ci(test-shards): the shard balance is derived on full-run sums while PR and merge_group runs use the affected set — the CLI shard (1/6) measures 34–36 min against 10–20 for the others and sets CI and queue wall time #22075's revert (PR Revert "ci(test-shards): grade the Test Core split on predicted shard wall and slice the CLI per run (#22415)" #22435,806b03e2) resolved. 17 files, +874 / −12, the reviewed net diff.Fixes #22258closed this card; no other card was closed by the body.origin/main: the nine in-processgetSessionreaders inplugin-auth(×4),plugin-webhooks,service-storage,plugin-sharing,service-settingsandservice-datasourceread throughinProcessSessionReadInput(headers)from@objectstack/types. Census onmain: 0 bare{ headers }readers left in those six packages. The cli half's pending note was corrected under ruling A.6073337286. At-tier PASS6073440660on8d9dcbb4and6075328673on3b548707(the landed head; it confirms the DELIBERATE CORRECTION).auth.api.getSessionreader in the framework renews a cookie session behind its cookie any more. The doors that split a session by other means (a/get-sessionre-dispatch, in-process vendor endpoint calls) are 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, which this landing unblocks.pm:dispatchedand the assignee are cleared in this stroke.Generated by Claude Code