Skip to content

refactor(plugin-auth): move the better-auth global after-hook into auth-manager-after-hook.ts (#25224 cut 12) - #25536

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-lg3wrj
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-lg3wrj

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Part of #25224
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160, 6364006498

The basis for the Clause-②: no line: this is a move. No accept set, route, refusal, code or answer changes, and the published surface is measured unchanged:

  • @objectstack/plugin-auth's dist/index.d.ts and dist/index.d.mts are raw byte-identical to base (0 diff lines, so the union-order normalisation has nothing to do). The positive control is the one ruling 6348891160 asks for.
  • Its ESM and CJS entries export the same 159 names at base and at head.

The .d.ts bar is ruling 6348891160 item 1 (on #25222, covering this card). Ruling 6364006498 is Sales's ruling A on this card. This is the fourth cut it applies to.

Cut 12 of the auth-manager.ts split: the global after-hook of the better-auth instance. It is the first of the three createAuthInstance slices the seat set (hooks.after, then hooks.before, then the rest of the method). #25224 stays open for the later cuts.

This follows the amended claim 6374301142, which amends 6372234293 on the file cut 11 left (#25524, landed note 6373433471). The shape is cuts 1 to 11's, which is #23050's, applied to a closure instead of a method. The seat ruled that in scope at 19:56Z, the same call as #25223's start() R3, with the bars listed below.

Branch: claude/project-thread-lg3wrj, the harness-pinned branch for this session, restarted from origin/main 1ff43b67ea after #25524 merged. It names no issue, so the card relation is this body's first line.

auth-manager.ts goes from 8,536 to 8,362 lines. The cut is +274 / −193 over 2 files: 467 changed lines, under the 3,000-line line. It is not a governed surface (check-governed-merges.mjs --branch HEAD: NOT governed). One commit: d79f55f402 refactor(plugin-auth): move the better-auth global after-hook into auth-manager-after-hook.ts.

The move

moved base lines proof lines tokens members it reads
the hooks.after arrow body in createAuthInstance :2834–:3016 185 610 8 methods

The lines are base 1ff43b67ea. The 185 proof lines are the body with its braces (:2833's { to :3017's }).

  • What it is: createAuthInstance (:1699, private, annotated Promise<Auth<any>>) builds one config literal for better-auth. Its hooks.after is createAuthMiddleware(async (ctx: any) => {BODY}): the after-hook every better-auth endpoint's response passes through.

  • What BODY does, in order (all of it moves, with its comments): the bearer rotation on impersonation, the echo of the installed session token, the session envelope completion, the two-factor re-enrolment reset, the sign-in outcome count with the refusal audit, the concurrent-session cap and the login stamp, the password-change stamp and history commit, the old-address notice on an e-mail change, the directory doors narrowed for an external caller, the invite-role refusal code, and the sign-up disableSignUp restore.

  • Where it goes: a new module, packages/plugins/plugin-auth/src/auth-manager-after-hook.ts (255 lines). BODY becomes the body of export async function globalAfterHook(this: AfterHookHost, ctx: any), re-indented eight spaces (four levels) and otherwise unedited. The module header gives the reasoning, in the form the eleven earlier auth-manager-*.ts modules use.

  • What stays: createAuthInstance keeps its place, docblock and signature, and hooks.after keeps its place in the literal. Its arrow is now:

    // [#25224 cut 12] The body moved to `auth-manager-after-hook.ts`; this
    // arrow hands it the members it reads (`AfterHookHost`).
    after: createAuthMiddleware((ctx: any) => {
      const host: AfterHookHost = {
        auditRefusedSignIn: (...a) => this.auditRefusedSignIn(...a),
        // … one closure per member, alphabetical, 8 in all
      };
      return globalAfterHook.call(host, ctx);
    }),

    Nothing else in createAuthInstance changes.

The seat's bars

Body.

  • BODY is the same TypeScript token sequence at base and in globalAfterHook: 185 lines, 610 tokens. Every line is the base line minus exactly eight spaces, and the parameter list (ctx: any) is the same text. Neither side has a return annotation.
  • async moves from the arrow to the function. The head arrow is not async, and it returns globalAfterHook.call(host, ctx), the function's own promise, unchanged. better-auth 1.7.12's createAuthMiddleware is better-call 1.4.4's createMiddleware, whose handler wrapper runs const response = await _handler(internalContext); inside its try (dist/middleware.mjs:15). So it awaits the same promise BODY settles, with the same value or rejection, and its catch sees the same error. The host literal and the .call cannot throw before that promise exists.
  • No captured locals. None of the locals createAuthInstance declares before the literal (betterAuth, createAuthMiddleware, setShouldSkipSessionRefresh, plugins, passwordHasher) is read in BODY. The binding proof below finds no identifier in BODY bound anywhere else in the method either.
  • No arguments, new.target or super (0 of each), where an arrow and a function differ. There is no bare this: BODY's only this uses are 9 member reads, this.X.

Receiver.

  • BODY's this was the AuthManager instance, the arrow's lexical receiver. It is now the host.
  • AfterHookHost names exactly the 8 members BODY reads, no more, each typed as the class member (AuthManager['x']): auditRefusedSignIn, enforceConcurrentCap, externalDirectoryDoorDeps, recordPasswordHistory, recordSignInOutcome, sendChangeEmailNotice, stampLastLogin and stampPasswordChangedAt.
  • All 8 are methods, so each goes as a late-bound closure, (...a) => this.x(...a). The method runs on the manager and is looked up when BODY calls it, so a spy or double set on the manager is still the one BODY reaches.
  • BODY reads no field, so there is no getter. No member BODY reads is a function-valued field, so there is nothing to disclose the way refactor(plugin-security): move the everyone anchor, the explanation, the sharing verdicts and the authored-row write verdict out of security-plugin.ts (#25223 cut 10) #25516 disclosed resolveKernelService.

Host lifetime. The host is built inside the arrow, once per run of the hook: each time better-auth runs hooks.after on an endpoint's response. The seat allowed once per createAuthInstance call; this is more often, and it is what every method stub in this series does. Nothing BODY reads is captured early, because each closure looks its method up on the manager at call time.

Readers. The decision letters are read at both ends below: 4 drop, as expected. No anchor entry is owed. Tenant census and symbol-anchor output are byte-identical at both ends, and the full plugin-auth suite passes at both ends.

Imports and evaluation order

Seven import lines leave auth-manager.ts, because BODY was their last reader there:

  • narrowDirectoryForExternalCaller (the line stays as import { type ExternalDirectoryDoorDeps } from './external-directory-door.js');
  • rotateCallerBearerOnImpersonation (a three-line import block), echoInstalledSessionToken, resetVerifiedOnTwoFactorReenrollment, codeInviteRoleNotFound, attachSessionToCredentialResponse and readRequestClientAddress.

SIGN_IN_EMAIL_PATH has other readers in auth-manager.ts, so both files import it. One import line is added after cut 11's, with a comment in the same form: import { globalAfterHook, type AfterHookHost } from './auth-manager-after-hook.js'. The new module imports the AuthManager type only.

esbuild drops a type-only import, so a module whose last value import leaves auth-manager.ts is now first evaluated later, when the new module is reached. I read each top level:

  • two-factor-rotated-token-echo, two-factor-reenrollment-verified-reset, invite-role-not-found-code, session-envelope-completion and external-directory-door are now reached only through the new module. None of the five imports anything. Their top levels are literals, interfaces and function declarations (plus two arrow consts in external-directory-door), so there is no side effect to reorder.
  • impersonation-bearer-rotation is still reached earlier, through admin-impersonate-endpoint.ts:137.
  • request-client-address is now first reached through auth-manager-request-routing.ts, before the new module. Its top level is one defineRequestState(() => undefined) from @better-auth/core/context, which creates a frozen ref object over the per-request store and registers nothing globally, so the order cannot be observed.
  • The new module's own top level is imports, one interface and one function declaration. Its import of auth-manager.ts is type-only, so there is no runtime cycle.

The decision-letter layer: −4, as expected

check-adr-anchors, measured at both ends. The gate exits 0 at both.

point anchored files citations files decision letters
base 1ff43b67ea 83 52,270 6,675 1,971
head d79f55f402 83 52,270 6,676 1,967
  • The four letters are in BODY's comments: ADR-0069 D2 (:2882, the account lockout counter), D7 (:2904, the login audit stamp) and D1 twice (:2918 and :2995, the password-changed stamps). They leave auth-manager.ts, which is anchored, for a module that is not. Their citations still resolve, so the citation count does not move.
  • No entry is owed. The auth-manager.ts entry has two paragraphs: ADR-0108 (better-auth's organization roles map) and ADR-0093 D2 (settleMembership and its two seams, user.create.after and session.create.before). Neither describes hooks.after, and no other entry names it. So, per the seat's bar, no entry is added for the new module.

Path readers

No path reader needs migrating: none reads a moved line.

  • The tenant-audit census. No census site moves: BODY has no engine write, no this.X.delete() and no elevation read. check-tenant-audit-census exits 0 at both with no --write, and its output is byte-identical. The --json document has 238 sites at both and 0 per-site differences with line numbers ignored. 2 sites are in auth-manager.ts at both (each 174 lines earlier at head, the cut's net), and 0 are in the new module. Only scannedSources (743 → 744) and measuredAt differ, so neither census document is edited.
  • check-dispatcher-error-vocabulary.mjs and api-error-code.pin.test.ts's authManagerFiles floor: BODY has no new APIError(. The gate reads 56 sites found and 56 classified at both; only its scanned-file count moves (3,420 → 3,421).
  • check-auth-mount-ledger.mjs: no base-path literal or declaration is in BODY.
  • The plugin-factory scans in better-auth-schema-parity.test.ts and managed-extension-fields.test.ts match better-auth/plugins… and @better-auth/… dynamic imports. BODY has no dynamic import, and both suites pass at head.
  • rate-limit-storage-isolation.test.ts walks the import graph from rate-limit-storage.ts, which reaches neither file. It passes at head.
  • The system-context census reads 149 elevation read sites at both, with byte-identical output.
  • Every symbol anchor into the file. Before the cut, every auth-manager.ts#symbol anchor in the tracked tree outside changelogs was mapped with symbolSegmentResolution from scripts/symbol-anchors.mjs. There are 29, and all 29 are declaration-class at base, at base with BODY blanked, and at head. None of their symbols occurs as a token on a line of BODY.

Proof

Builds and suites ran through os-verify-lock, and every exit code was captured before any pipe. Base 1ff43b67ea. A full turbo run build ran first. Then plugin-auth was built and its suite run at base, and built and run again at head, on the same dependency dists.

1. The moved text.

  • Token identity: the base arrow body and the module function body are the same TypeScript token sequence (scanner tokens, kind and text, trivia skipped, each template literal re-scanned at its substitutions as the parser does): 185 lines and 610 tokens, each line the base line minus eight spaces.

  • The head arrow's shape, checked by the same run: not async, the same parameters, one host literal whose keys are exactly AfterHookHost's and whose every value is (...a) => this.<key>(...a), then return globalAfterHook.call(host, ctx), and nothing else.

  • Binding identity: a second proof walks all 181 identifiers of BODY at base and in the module, in order, and resolves each with the TypeScript checker. All 181 resolve the same way at both ends:

    • 38 to the ctx parameter;
    • 56 to a declaration inside BODY, the same declaring identifier by position;
    • 9 to the receiver's members (AuthManager at base, AfterHookHost at head);
    • 8 to imports with the same specifier and imported name;
    • 70 to no declaration: the globals Error and undefined, and property names on untyped values.

    None resolves to a createAuthInstance local or to a top-level declaration of either file.

  • Negative controls: both proofs read the module text, and each control changes it in memory only; the file is untouched.

    • Token proof: await rotateCallerBearerOnImpersonation(ctx); changed to void … fails the token and line checks, and the run exits 1.
    • Binding proof: the module's readRequestClientAddress import pointed at another specifier gives 1 difference, and the run exits 1.
  • An independent reading: git diff --color-moved=plain --color-moved-ws=allow-indentation-change 1ff43b67ea d79f55f402 over the source files marks 188 of the 193 deleted lines and 194 of the 274 added lines as moved.

    • The 5 unmoved deletions are the external-directory-door import line (now type-only), the three-line rotateCallerBearerOnImpersonation import block (now one line in the module), and the after: createAuthMiddleware(async (ctx: any) => { line (now without async).
    • The other added lines are the module header, the host interface and its docblock, the function signature, the arrow's host literal and return, and the two new import comments and lines.

2. .d.ts and exports (ruling 6348891160 items 1 and 2).

file base and head raw diff lines
dist/index.d.ts raw 1b3a48357d137155de33104277667a922aab62df73b418b03894c56faaacd0ba 0
dist/index.d.ts normalised cee7303ccdfe72794efd588c85b628419d4253d730b5a0c5a3f1cdb8dddb4443
dist/index.d.mts raw b5e8dc982b634f0a5108b1b533626a68767f2da5a56f72dba801059b149e2872 0
dist/index.d.mts normalised 562d0a4ce656f97aeb937541482cdf531b61d7c75a005918784bf0a52d032dfc
  • The normaliser, verbatim (the ruling's one-liner): node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]*"(?:\s*\|\s*"[^"\n]*")+/g,r=>r.split(/\s*\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sum.
  • The raw files are byte-identical, so no union-order swap occurs: items 1(b) and 1(d) hold with nothing to account for.
  • Positive control: the head dist/index.d.ts with one private member made public (private createAuthInstance; → createAuthInstance;, line 1712, a one-line change) hashes raw 35b18682171cd394898c9e56bc8d5272ccf20aeff0d1966fc568cb5e566a08df and normalised e69c1b0157b45e48bbc16bd64eb939abf9be33ccb31553485df93ef05aac6fc3. Both differ from the readings above.
  • Export lists: ESM dist/index.mjs and CJS dist/index.js each have 159 names at base and at head, the same list (the sha256 of the sorted names is 3abb126f5babb701… on all four). globalAfterHook and AfterHookHost are not among them, and dist/index.d.ts names neither.
  • The bundle: 18 files in dist/ at both, the same list. Only index.js, index.mjs and their two .map files differ, as a move of code inside the bundle must. tsup bundles the new module into the root entry (splitting: false).

3. Tests and lint.

  • pnpm exec vitest run --maxWorkers=3 in packages/plugins/plugin-auth: base Test Files 191 passed (191), Tests 3607 passed | 10 skipped (3617), exit 0. Head: the same counts, exit 0.
  • pnpm --filter @objectstack/plugin-auth typecheck (both tsc projects and check:test-typecheck): exit 0. pnpm --filter @objectstack/runtime typecheck: exit 0.
  • eslint --no-inline-config (the root lint script's binary and flags) over the 2 source files: exit 0.

4. The censuses, the measurement and the anchors, at base and at head.

  • The tenant-audit census, as above: byte-identical gate output, 238 sites at both, 0 per-site differences line-blind, 0 unresolved and 0 unledgered at both.

  • scripts/measure-durability-swallow-family.mjs (a measurement, not a gate): 77 members and 84 quiet rows at both. The members are the same with line numbers ignored, and the quiet rows are identical. Only the scanned file count moves (3,470 → 3,471, the new module). tryStatements (3,639), droppedNotAwaited (239) and every other statistic are the same: BODY's 4 try blocks move whole.

  • These checkers were run by hand at base and at head, because dispatch-gates does not derive the ADR one. All six exit 0 at both. Five print byte-identical output at both (cmp clean):

    • check-adr-symbol-anchors: 2,384 anchors across 142 records, 466 symbol (440 declaration, 26 literal);
    • check-scripts-symbol-anchors: 4,070 anchors;
    • check-spec-docblock-symbol-anchors: 5,076 anchors;
    • check-system-context-census: 149 elevation read sites;
    • check-platform-checklist: 16 areas, 329 items.

    The sixth, check-adr-anchors, differs only in the file count and the decision letters, as in the table above.

5. Gates.

Changeset

None: skip-changeset, per ruling 6348891160 item 4. The published declarations and export lists are unchanged, and the bundle's bytes move only because code moved inside it.


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr

…th-manager-after-hook.ts

The body of the arrow createAuthInstance passes to createAuthMiddleware
for hooks.after moves unedited into globalAfterHook, re-indented four
levels. hooks.after keeps its place in the config literal; its arrow now
builds one host of the eight AuthManager methods the body reads, as
late-bound closures, and returns globalAfterHook.call(host, ctx). The
async moved from the arrow to the function, so the middleware awaits the
same promise the body settles. The arrow read no local of
createAuthInstance, and the body uses no arguments, new.target or super.
Seven imports leave auth-manager.ts with it; SIGN_IN_EMAIL_PATH is
imported by both files.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSXBc2GCBwiAL7vV4kWkyr
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

19 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 21 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 74496c64b0637103f87cceec3f37932a48813e65 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 7d673bb0fbe091422ccba9b780885e52e6278a10 — the merge of head d79f55f402986e8577c4586466fc094071ea73b0 into base 74496c64b0637103f87cceec3f37932a48813e65, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7d673bb0fbe091422ccba9b780885e52e6278a10 && git checkout 7d673bb0fbe091422ccba9b780885e52e6278a10
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 74496c64b0637103f87cceec3f37932a48813e65 d79f55f402986e8577c4586466fc094071ea73b0 && git checkout -B drift-repro 74496c64b0637103f87cceec3f37932a48813e65 && git merge --no-ff d79f55f402986e8577c4586466fc094071ea73b0

node scripts/docs-audit/affected-docs.mjs --json 74496c64b0637103f87cceec3f37932a48813e65

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-23T21:10Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25224 cut 12, hooks.after (claim 6374301142; os-dev-report 6374754398) · reviewed head d79f55f402 · merge base 1ff43b67ea

  • Shape: the hooks.after arrow body in createAuthInstance moves unedited into auth-manager-after-hook.ts as globalAfterHook(this: AfterHookHost, ctx).
  • The seat's bars, as measured (report):
    • Body: 185 lines / 610 tokens, token-identical, and a negative control fails. async moves to the function, and the arrow returns that promise unchanged (better-call's createMiddleware awaits it inside its try).
    • Closure hazards: 0 captured locals of createAuthInstance. BODY uses no arguments, new.target, super or bare this. A TS-checker binding proof resolves 181 of 181 identifiers the same way, and it has its own failing control.
    • Receiver: AfterHookHost holds exactly the 8 members BODY reads. All are methods, carried as late-bound closures. No field, and no function-valued member.
    • Host lifetime: the host is built inside the arrow, once per hook run, so nothing is captured eagerly.
  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree:
    • turbo run build --filter=@objectstack/plugin-auth at 1ff43b67ea and at this head.
    • Results at both ends:
      • dist/index.d.ts: raw 1b3a48357d137155… / normalised cee7303ccdfe7279….
      • dist/index.d.mts: raw b5e8dc982b634f0a… / normalised 562d0a4ce656f97a….
      • The raw diff is 0 lines.
    • The seat's positive control makes head line 347, private options;, public. That moves both hashes, to raw 51a55969ad8afac6… and normalised 66bcd34e7ea6963c….
    • Exports: 159 / 159 (report).
  • Gates re-run by the seat, all exit 0 at base and at head:
    • check-adr-symbol-anchors: byte-identical output.
    • check-tenant-audit-census: byte-identical output, 238 sites.
    • check-adr-anchors
    • check-dispatcher-error-vocabulary
  • Decision-letter count, read by the seat: 1971 at base and 1967 at head, with anchored files 83 at both.
    • The four that leave are ADR-0069 D1 ×2, D2 and D7, as the plan said.
    • No paragraph of the auth-manager.ts entry describes hooks.after, so no entry is owed. This is the same shape as cuts 5 and 10.
  • Tests and gates (report): 191 files, 3607 passed / 10 skipped at both ends. 54 derived gates exit 0, with 0 pages of docs drift.
  • CI on this head: 34 success and 12 expected skips, none failed or pending (46 runs).
  • Mergeable: git merge-tree onto today's main (8ef2a96bd5) is clean. No other open PR touches plugin-auth.
  • Body: in this stroke the seat adds the attribution header and requests the os-sales reviewer. The footers are present.
  • Declarations: refactor(plugin-auth), Part of #25224, Clause-②: no, Metadata-protocol: none, skip-changeset, Ruling-ref: 6348891160, 6364006498. No contract-review record or dogfood battery is owed.
  • Landing: not governed (check-governed-merges --pr 25536: 467 lines). Ready + auto-merge through the relay. The card stays open and dispatched.
  • Next: cut 13 (hooks.before) is claimed afresh on the merged main. It edits readers outside the package, so check-governed-merges runs on its trial file list before the claim.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 23, 2026 21:11
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 21:11
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 21:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 93a6ba4 Oct 23, 2026
54 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-lg3wrj branch October 23, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants