Skip to content

fix(plugin-auth): build one better-auth instance per boot, so the RFC 8707 resource row is seeded once - #17525

Merged
os-sales merged 2 commits into
mainfrom
claude/issue-17176-oauth-resource-seed
Sep 10, 2026
Merged

fix(plugin-auth): build one better-auth instance per boot, so the RFC 8707 resource row is seeded once#17525
os-sales merged 2 commits into
mainfrom
claude/issue-17176-oauth-resource-seed

Conversation

@claude

@claude claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #17176

Clause-②: no

Both title limbs were falsified first. One of them turned out to name a real defect on OUR side.

This is the re-cut of the card, not the card as filed. Every reading below is on origin/main, and every zero carries a control that fires.

Limb 2 — "its UNIQUE refusal is now logged at ERROR" — FALSIFIED

All three write doors in packages/objectql/src/engine.ts log at warn, unchanged since 0780e8848 (#17211, closing #17052):

door line call
insert :11069 this.logger.warn('Insert operation failed', …)
update :12624 this.logger.warn('Update operation failed', …)
delete :14081 this.logger.warn('Delete operation failed', …)

Control (so the warn-only reading is not a dead grep): logger.error occurs 5 times in the same file — :7725 'Failed to connect driver', :8532 'Error disconnecting driver', :9814 'Find operation failed', :13202, :14827. The probe finds error where error exists and finds warn at the three write doors because that is what is there.

⇒ The "an idempotent step reporting as a failure" framing is gone. ⛔ Nothing in packages/objectql is touched here; #17052's ruling stands.

Limb 1 — "the bootstrap row is re-inserted on every boot" — FALSIFIED of the vendor

@better-auth/oauth-provider 1.7.2 does not re-insert. Its seedResources() — called from the plugin's own init — is already check-then-insert: findOne by identifier, then create only on a miss, with the UNIQUE refusal caught and treated as a no-op that its docblock declares by name ("one wins, the other catches the constraint error"). resourceSeedMode defaults to "insertOnly", documented as "only inserts rows whose identifier is not already present".

Measured, not read: seedsOnce in the new test boots the REAL provider from the REAL options AuthManager produces, three times over one shared store. Insert attempts: 1, 1, 1 — the second and third inits attempt none, and zero refusals reach the unique index.

Control: blinding the vendor's existence check (findOne on the resource model forced to answer null) takes that count from 1 to 3 and reddens the pin. The green is a reading of the vendor's check, not an assertion that cannot fail.

What IS ours, and it is the whole of this card

AuthManager.getOrCreateAuth() was an async lazy initializer with no single-flight guard:

if (!this.auth) {
  this.auth = await this.createAuthInstance();
}
return this.auth;

this.auth is assigned only after createAuthInstance() resolves, and that function awaits a dynamic import('better-auth'), buildPluginList(), resolvePasswordHasher() and finally better-auth's own $context. Every caller arriving inside that window reads null and starts its own build.

Measured on the unmodified tree, with no mocks at all: three concurrent getAuthInstance() calls returned three DISTINCT better-auth instances. Sequential calls returned one.

The boot has such callers. auth-plugin.ts dispatches registerOidcDiscoveryRoutes() with void from its route-mounting kernel:ready hook — that hook returns while the call is still pending (:3096, calling getAuthInstance() at :3111) — and a later kernel:ready hook reads the instantiated social providers off the instance for the account-issuer backfill (:1106 into :374).

Each duplicate instance re-runs every better-auth plugin's init, so each one runs the resource seed. On a warm database all of them find the row and insert nothing. On a fresh one they all miss on findOne together, all insert, and the unique index refuses all but the first.

⇒ That is the reported line, and it is first-boot-on-a-fresh-database only — exactly the shape the repo:hotcrm seat measured downstream (boot 1: one occurrence; boots 2–4: none, each paired with a Server is ready control) and exactly what packages/objectql/src/engine.ts:493 records as the cost #17052 accepted.

Measured, as a differential against seedsOnce: same store, same options, one variable — two overlapping inits on a fresh store attempt 2 inserts and the unique index refuses 1, while the row ends up present exactly once. Nothing breaks; only the log line shows it.

The change

packages/plugins/plugin-auth/src/auth-manager.ts only, plus one new test file and a patch changeset.

getOrCreateAuth() now holds the in-flight build in a new private slot so concurrent callers share it. A rejected build is not cached — the slot clears and the next caller retries, which is what the un-serialised form did.

Two consequences of the new slot, both handled in the same edit so it leaves no half-state:

  • setRuntimeBaseUrl() reports "already created" for a build in flight. It has read (or is about to read) the standing config and is now the instance every later caller receives, so reporting only on the materialised memo would let the call silently no-op.
  • applyConfigPatch() discards an in-flight build alongside a materialised one. It was composed from the pre-patch config; getOrCreateAuth() checks its own identity before installing, so a disowned build resolves to its callers and installs nothing.

⛔ Not done, deliberately: no log level is changed anywhere, and no upsert is introduced. The triage note asked for a genuinely idempotent step over a quieter log — the step is already check-then-insert inside the vendor, so the cure is not doing it twice. That also answers the driver question: this needs no upsert support from any driver, because there is no second insert to reconcile on any of them.

Clause-② working

Limb A — does this widen the accept set? No. Nothing that was refused becomes legal. It is a pure narrowing in every direction: fewer instances per manager, and two invalidation paths that now discard a build they previously adopted.

Limb B — does it add or change a public surface? No. The new slot is private. No export is added, removed or renamed; getAuthInstance(), getApi(), getAuthContext(), ownsRoute(), setRuntimeBaseUrl() and applyConfigPatch() keep their signatures; no key moves on any published payload. pnpm check:api-surface territory (packages/spec) is untouched — this card never reached it.

Clause-②: no, and the measurement agrees with the claim-time reading.

Verification

RED before GREEN, by mutating the committed state. The mutation restored the pre-fix if (!this.auth) shape. git hash-object before 36154af3191bf549e9a999a5489a58627aba67a8 (equal to the HEAD blob), after 0a247a91c61a524d6fa2e3e20b6608567368eeea; injected-marker occurrence count 0 → 1. Under the mutation: singleFlight fails with "expected 3 to be 1" and the applyConfigPatch pin fails; the two vendor-characterisation checks stay green, which is the predicted direction — they do not depend on this fix. Restored by state: git diff HEAD empty, git status --porcelain empty, file hash back to the HEAD blob byte-for-byte.

  • pnpm --filter @objectstack/plugin-auth test107 files / 2274 tests passed; lock VERDICT command-exit 0.
  • pnpm --filter @objectstack/plugin-auth build && … typecheck — all three legs pass (tsc --noEmit, the examples project, check:test-typecheck); lock VERDICT command-exit 0. An earlier run exited 2 on a missing dist/ — a prerequisite, not a finding; building the package first cleared it.
  • pnpm exec eslint . --no-inline-config --format json — repo-wide, 6581 files, 0 errors, 0 warnings, exit 0. No narrowing claimed: this is the whole population as eslint's own config resolves it.
  • Gates derived from the actual diff with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, all 61 run, reconciled with --ran carrying every exit code: 59 exit 0, 2 exit 3 = PREREQUISITE NOT MET = NOT MEASUREDcheck:dual-build-cjs-loads and check:type-check-debt, both refusing because packages across the repo have no dist/. ⛔ Neither is reported as a pass. CI builds the farm and measures them there.

Acceptance notes — noted, not filed

  • getOrCreateAuth() is not the only async lazy initializer of this shape in the package, but no other one was measured to construct anything whose init performs I/O, so none is a defect on today's evidence. Successor: whoever next touches an in-flight-memo seam in this file. Noted, not filed.
  • The un-awaited void this.registerOidcDiscoveryRoutes(...) in auth-plugin.ts is what makes the overlap reachable at boot. It is a legitimate fire-and-forget with its own catch, and with the single-flight fix in place it costs nothing; it is also inside PR feat(auth)!: adopt better-auth's account-issuer rollback — drop sys_account.issuer, retire the backfill, lift the family to 1.7.3 #17454's held file set, so it is not touched here. Noted, not filed.

Fence

⛔ Nothing in PR #17454's held set is touched: auth-schema-config.ts, auth-plugin.ts, index.ts, objectql-adapter.ts, admin-user-endpoints.ts, package.json, auth-manager.test.ts and packages/spec/src/migrations/registry.ts are all unmodified — the new pins are in a new file of their own. packages/spec is untouched entirely.


Generated by Claude Code

`getOrCreateAuth()` assigned `this.auth` only after `createAuthInstance()`
resolved, so every caller arriving inside that window started its own build.
Overlapping boot callers therefore constructed one better-auth instance each,
and each instance re-ran `@better-auth/oauth-provider`'s `init` — whose
check-then-insert seed of the RFC 8707 `sys_oauth_resource` row all miss
together on a fresh database, leaving the unique index to refuse all but the
first.

Hold the in-flight build so concurrent callers share it. The new slot is also
read by `setRuntimeBaseUrl()` (a build in flight counts as created) and cleared
by `applyConfigPatch()` (a build composed from the pre-patch config is
discarded rather than adopted).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ToDPcx9AESFubJkDiFMtKW
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-auth, touching 5 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/kernel/contracts/auth-service.mdx (via AuthManager (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx (via AuthManager (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx (via AuthManager (symbol, a top-level class))
What this run could not see
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 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; 100 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.

Coarse fallback — 13 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 f8e5790593ed6da5aecb600477a704a3c1db7951packageMentionDocs.

Which tree this was computed on

This run read content/docs from c880425ccbc8d1afb51a9199b166da8e98112645 — the merge of head 311b4eec7354312d7e8660ad61cd172c504e54ec into base f8e5790593ed6da5aecb600477a704a3c1db7951, 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 c880425ccbc8d1afb51a9199b166da8e98112645 && git checkout c880425ccbc8d1afb51a9199b166da8e98112645
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f8e5790593ed6da5aecb600477a704a3c1db7951 311b4eec7354312d7e8660ad61cd172c504e54ec && git checkout -B drift-repro f8e5790593ed6da5aecb600477a704a3c1db7951 && git merge --no-ff 311b4eec7354312d7e8660ad61cd172c504e54ec

node scripts/docs-audit/affected-docs.mjs --json f8e5790593ed6da5aecb600477a704a3c1db7951

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs f8e5790593ed6da5aecb600477a704a3c1db7951 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 10, 2026
@claude

claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

docs-drift-check: all three rows answered, plus the emitter-blind half

Tree. Answered on c880425ccbc8d1afb51a9199b166da8e98112645, the tree the bot named. I fetched it as refs/pull/17525/merge and it resolves to that exact sha. On it, this PR's diff is the same three files — .changeset/auth-manager-single-flight-instance.md, packages/plugins/plugin-auth/src/auth-manager-instance-single-flight.test.ts, packages/plugins/plugin-auth/src/auth-manager.ts, 357 insertions / 7 deletions. ⚠️ The bot's own note that its checkout "carried uncommitted changes, so the commit above does not fully identify what was read" is carried, not ignored: I do not treat its three-page list as exhaustive, which is exactly why the hand search below is scoped to the whole of content/docs/** rather than to its rows.

Row 1 — content/docs/kernel/contracts/auth-service.mdx — NOT falsified

Read in full (172 lines). The sentences that bear on this diff:

  • The interface comment on line 34: "Resolve the session API, creating the auth instance on first use." Still exactly true. The change alters how many builds one "first use" starts when several of them overlap, never whether a build happens on first use. If anything it is the sentence's stricter reading: before, two simultaneous first uses each created an instance.
  • "api and getApi() are the same session handle by two routes … the shipped plugin-auth registers an AuthManager, which has no api member at all … Read api ?? await getApi?.()." Untouched — no member is added, removed or renamed, and getApi() keeps its signature and its laziness.
  • Line 43, "The MCP resource identifier (RFC 8707 resource / token aud)" — describes the identifier getMcpResourceUrl() returns, not how or how often the sys_oauth_resource row is seeded. getMcpResourceUrl() is not modified.

Measured zero for the rest, with a firing control: setRuntimeBaseUrl, applyConfigPatch, getOrCreateAuth, createAuthInstance, getAuthInstance, getAuthContext, ownsRoute, sys_oauth_resource, oauthProvider all occur 0 times on this page, while the control term auth occurs 40 times — the grep reaches the file.

Row 2 — content/docs/kernel/services-checklist.mdx — NOT falsified

Its single AuthManager mention is an attribution line in section 4: "Implementation: AuthPluginAuthManager (@objectstack/plugin-auth, built on better-auth)", beside "Route Mount: /api/v1/auth — only exposed when a plugin registers the auth service". I also read the whole of section 4's "Where each capability lives" table, including its OAuth2 / OIDC row ("@objectstack/plugin-auth@better-auth/oauth-provider, @better-auth/sso") and its Identity Provider row ("better-auth sessions + jose JWTs (auth-manager.ts)").

Every one of those is a statement about which package owns which capability and where the routes mount. None states anything about the instance's lifecycle, its memoization, when it is built, or how the resource row is seeded. Same measured zero as Row 1 for the nine identifiers, control auth = 38 hits.

Row 3 — content/docs/permissions/authentication.mdx — NOT falsified, and one sentence is strengthened

Its single AuthManager mention is a configuration-surface pointer: "Tune via AuthManager phoneOtp (cooldownSeconds / maxPerHour / allowedAttempts / expiresIn / otpLength)." — a config key list, untouched.

I went after the two declared consequences specifically on this page, since that is where a falsification would hide:

  • setRuntimeBaseUrl(). baseUrl occurs 12 times; I read every one. All twelve are configuration examples inside AuthPlugin / stack snippets (baseUrl: 'http://localhost:3000' and the production-conditional at line 1147). Not one documents a runtime base-URL mutation, or when such a change takes effect against an already-built instance. The method name itself occurs zero times.
  • applyConfigPatch(). The load-bearing sentence is line 533: "Changes take effect immediately; no restart is required." — scoped to the Setup → Authentication toggles, which are precisely what flows through applyConfigPatch(). ⭐ This change makes that sentence more true, not less: previously a build already in flight when a patch landed would still install itself, so a setting changed inside that window was silently ignored by the instance every later caller received. It is now discarded and the next caller rebuilds from the patched config. The page needs no edit; the window it quietly excluded is closed.
  • The only other restart (line 562) is about restarting the sign-in flow after 2FA attempt caps, and the only instance (line 795) is the English adverb "for instance". lazily, lazy, on first use, per boot, singleton, rebuild all occur 0 times.

The emitter-blind half — hand search over all of content/docs/**

Run on the same tree, deliberately NOT scoped to the bot's rows. Every zero carries a control of the identical grep shape.

(A) RFC 8707 / sys_oauth_resource seeding prose. Five hits for sys_oauth_resource|oauth_resource|RFC 8707|8707|resourceSeedMode|oauthResource:

  • content/docs/kernel/contracts/auth-service.mdx:43 — read above.
  • content/docs/releases/v17/17-1.mdx:313 — a false positive: #8707 is an issue number, not the RFC.
  • content/docs/releases/v13.mdx:129, v17/17-3.mdx:462 (the identifier column narrowing 1024 → 255), v17/17-4.mdx:418 ("MCP OAuth can complete again: the MCP resource is registered as an RFC 8707 resource and DCR-registered clients are linked to it"). All three are release-owned pages, historical records of what shipped, and none makes a per-boot or re-insert claim that this diff touches — the resource still ends up registered exactly once, which is what they assert. ⛔ Release pages are not edited in a code PR in any case.

Control: oauth hits across 44 files in content/docs/**.

(B) Auth-instance construction / per-boot lifecycle prose. Searching better-auth instance|auth instance|lazily built|created lazily|built lazily|one instance per|per boot|per-boot|instance is built|instance is created|singleton across content/docs/** outside releases/: the only hit about this instance is auth-service.mdx:34, already answered. Every other hit is about a different subject — kernel service scopes (services.mdx:60, service-registry.mdx, cluster.mdx), CLI boot-time gate logging (deployment/cli.mdx:1030, :1046), and sharing-rule re-evaluation (permissions-matrix.mdx:159). Control: better-auth hits 108 lines in the same scope.

(C) The changed method names anywhere in the docs corpus. setRuntimeBaseUrl|applyConfigPatch|getOrCreateAuth|createAuthInstance across all of content/docs/**: zero hits, exit 1. Control, identical grep shape: getAuthInstance|handleRequest|getCurrentUser hits in four files (api/error-handling-client.mdx, concepts/architecture.mdx, kernel/contracts/auth-service.mdx 7 times, permissions/authentication.mdx). The zero is a reading, not a bad grep.

No documentation change is owed by this diff. No page documents either changed method, none states the auth instance's construction cadence beyond "on first use" (which remains true and is tightened), and no hand-written page describes the resource seeding at all.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

ACCEPT — 2026-09-10T19:22Z, head 311b4eec7354312d7e8660ad61cd172c504e54ec, mergeable_state: clean.

Landing pre-checks: no in-seat contract review owed (Clause-②: no, no needs:contract-review on either carrier); check-clause2-carriers --pair 17525EXIT 0; distinct: 33 | {success: 30, skipped: 3}, NOT GREEN: none — the whole set, not a required subset.

The one question this seat held open, answered from the diff

The changeset is graded patch while the delivery honestly declares two behaviour changes on published methods. That is the kind of pairing that usually means the grade is wrong, so I read the hunks rather than waving it through:

   setRuntimeBaseUrl(url: string): void {
-    if (this.auth) {
+    if (this.auth || this.authBuild) {
       console.warn('[AuthManager] setRuntimeBaseUrl() called after the auth instance was already created — ignoring. …

⇒ Neither change moves a succeeding path. Both take a case that previously produced a silently wrong or silently ineffective result and make it correct or explicitly reported:

  • setRuntimeBaseUrl() already warned-and-ignored once the instance existed. Inside the build window it used to set the field and say nothing — while the in-flight build had already read the config, so the call was ineffective anyway. It now says so. Silently ineffective → explicitly ignored.
  • applyConfigPatch() used to let a build composed from the pre-patch configuration install itself, so a setting changed inside that window was silently ignored by the instance every later caller received. It is now discarded and the next caller rebuilds from the patched config. Silently ignored patch → patch actually takes effect.

patch is right — this is AGENTS.md's "a bug fix in a released package takes a patch changeset", not a contract change. And Limb A is right for the same reason: setRuntimeBaseUrl now refuses in a window where it previously accepted-and-did-nothing, which is a narrowing, so Clause-②: no holds.

What the delivery established, beyond fixing it

  • Both of the card's contested premises are resolved by ONE mechanism, which is the part worth keeping. Limb 2 (ERROR) was already false — all three write doors in packages/objectql/src/engine.ts log at warn since fix(objectql): report a refused write at warn, not error — the caller was already told #17211, with logger.error present 5× in the same file as the firing control. Limb 1 ("every boot") is false of the vendor: @better-auth/oauth-provider's seedResources() is already check-then-insert, measured at 1/1/1 inserts and 0 refusals over three sequential inits, with a control — blinding the vendor's findOne takes that to 3 and reddens the pin.
  • The defect is ours, and it is a race, not a re-insert. getOrCreateAuth() assigned this.auth only after createAuthInstance() resolved, and that awaits a dynamic import, the plugin list, the password hasher and better-auth's own $context. Measured on the unmodified tree with no mocks: three concurrent getAuthInstance() calls returned three distinct instances; sequential calls returned one. Each duplicate re-runs every plugin init, so each runs the seed — on a warm DB all find the row and insert nothing, on a fresh DB all miss together and all insert. Differential on one variable: two overlapping inits on a fresh store ⇒ 2 insert attempts, 1 UNIQUE refusal, row present exactly once.
  • That prediction matches the downstream reading nobody could previously reconcile. The repo:hotcrm seat measured boot 1 = 1 occurrence, boots 2–4 = 0, each zero with a Server is ready control. Two concurrent instances on a fresh database predict exactly that, and "re-inserted on every boot" predicts something else. The card's two contradictory premises were one mechanism seen from two ends.
  • Boot-time overlap is a code reading of two named seams, not an assumption: auth-plugin.ts dispatches registerOidcDiscoveryRoutes() with void from its route-mounting kernel:ready hook, and a later kernel:ready hook reads the instance for the account-issuer backfill.
  • No log level was changed and no upsert was introduced. The step is already check-then-insert inside the vendor, so the cure is not doing it twice — which is also why this needs no upsert support from any driver, the fallback triage asked about.
  • RED/GREEN by state: mutation proven on disk by blob hash and marker count, restored to the HEAD blob byte-for-byte with git diff HEAD and git status --porcelain both empty, under a trap with absolute paths. Under the ablation the two vendor-characterisation checks stayed green — the predicted direction, since they do not depend on this fix.
  • 61 gates derived from the actual diff, all 61 run, 59 exit 0. Two exit 3 = PREREQUISITE NOT MET, recorded as NOT MEASURED and never rounded up. A third item is declared not measured on its own initiative: an end-to-end fresh-boot occurrence count in a real app, with the reason and what stands in for it stated rather than skipped silently.
  • Fence held: write surface is auth-manager.ts + one new test file + the changeset. Nothing PR feat(auth)!: adopt better-auth's account-issuer rollback — drop sys_account.issuer, retire the backfill, lift the family to 1.7.3 #17454 holds, nothing under packages/spec, and auth-manager.test.ts untouched.
  • docs-drift answered in full on the tree the bot named, all three rows NOT falsified with the sentences read, plus the emitter-blind hand search the bot says it can never do — every zero with a firing control of identical shape.

⚠️ Commit a227a9c9e carries a model-named trailer. That is this seat's defect — my dispatch order specified it against AGENTS.md 440–444 — and it was pushed before my correction arrived. Per that same passage landed history is not rewritten, so it stands; the dev reported it rather than amending, which is correct. Correction 109 on seat post #6021; the wider measurement is on #17280, which owns that gap.

Flipped ready and armed. ⛔ Not approved and ⛔ not merged by this seat.


Generated by Claude Code

@os-sales
os-sales enabled auto-merge September 10, 2026 19:22
@os-sales
os-sales added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit efa2533 Sep 10, 2026
35 checks passed
@os-sales
os-sales deleted the claude/issue-17176-oauth-resource-seed branch September 10, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants