Skip to content

Rework the DCR OAuth client model: per-AS reuse, picker isolation, GC#1276

Open
RhysSullivan wants to merge 18 commits into
mainfrom
fix/dcr-oauth-client-model
Open

Rework the DCR OAuth client model: per-AS reuse, picker isolation, GC#1276
RhysSullivan wants to merge 18 commits into
mainfrom
fix/dcr-oauth-client-model

Conversation

@RhysSullivan

Copy link
Copy Markdown
Owner

Fixes #1120. Supersedes #1139 and #1142.

Users keep seeing DCR-minted MCP clients ("Cloudflare mcp", "Cloudflare mcp 2", …) in the Add-connection OAuth2 app picker for unrelated integrations on the same domain — e.g. the plain Cloudflare REST API offering clients registered against mcp.cloudflare.com. Root cause: the picker matched apps by registrable root domain only, and DCR re-registered a fresh client on every connect, so duplicates accumulated and then leaked across every integration sharing an eTLD+1.

This replaces the model instead of patching the filter. The principle: DCR clients are plumbing, not apps — they never appear in the connect picker; manual apps are matched by recorded intent first, domain heuristic second, and near-misses are grouped honestly instead of silently mixed in.

What changed

Per-AS DCR client reuse (no more duplicates)

  • oauth_client gains a nullable origin_issuer column (cloud 0008_slow_mephisto, local 0003_graceful_the_fallen). For new DCR registrations it stores the discovered AS issuer; for backfilled legacy rows, the registrable origin derived from token_url.
  • registerDynamicClient is now get-or-register: keyed on (owner, canonical AS issuer), with the resource differentiating only once a second resource shows up on the same AS. Legacy null-issuer rows are found via a token-host fallback; candidate ordering is deterministic (oldest first). A resource-less request never grabs a resource-scoped client.
  • Slugs are deterministic dcr-<host> (with uniqueDcrSlug for the resource-differentiated case) instead of numeric suffixing.

Picker redesign

  • Clients with origin.kind === "dynamic_client_registration" (including legacy rows classified by heuristic) are filtered out of the app picker entirely.
  • Remaining manual apps render in two tiers: matched (recorded origin_integration or exact endpoint-host match) and a collapsed, subdued "Other apps on this provider" group for eTLD+1-only matches — never silently merged. Registering a manual app from within an integration's dialog now stamps origin_integration so future matching is exact.
  • BYO manual apps stay selectable for DCR-capable integrations (enterprise case); only auto-registered clients are hidden.
  • DCR clients remain manageable: a collapsed "Auto-registered clients" section on the OAuth2 tab lists them (view/delete only, not selectable as the connect app).

GC of accumulated duplicates

  • One-time cleanup on both stacks — local as a stamped boot data-migration, cloud as a stamped code migration: delete rows that are DCR-classified and referenced by zero connections; backfill origin_issuer on survivors so legacy clients are discoverable by the reuse lookup. Live duplicates sharing (owner, issuer) are left alone; deterministic ordering picks one at reuse time.
  • Classification lives in one place (isDcrClassifiedRow), and registrable-domain matching now uses the full public suffix list via tldts instead of a hand-rolled suffix set (fixes .co.in / .com.cn / .co.za style domains).

Safety: connections resolve their client by stored (owner, slug) — refresh and reconnect never go through the picker, so hiding DCR clients cannot break existing connections. GC only deletes rows with zero referencing connections.

Tests

  • oauth-register-dynamic.test.ts — reuse hits/misses across owner/AS/resource, legacy fallback, slug determinism
  • oauth-gc.test.ts — GC decision matrix (DCR+orphaned deleted, referenced kept, manual kept, heuristic-classified treated as DCR, backfill, idempotency), multi-part-TLD domains
  • use-effective-oauth-client.test.ts + dcr-root-domain-isolation.test.ts (react) — DCR excluded, intent beats domain, domain-only lands tier 2, cross-integration leak regression, scheme-less endpoint regression
  • e2e/cloud/dcr-root-domain-isolation.test.ts — over-the-wire DCR connect, no-duplicate reuse, API projection
  • Full gates: typecheck 42/42, lint clean, core-sdk 390 / react 207 / cloud migration 3 tests, e2e green

Notes for review

  • Merge-time check: the drizzle journal indices (cloud idx 8, local idx 3) are free on main today; if another migration lands first, re-run git diff origin/main...HEAD -- apps/cloud/drizzle/meta/_journal.json apps/local/drizzle/meta/_journal.json and renumber.
  • Likely conflicts with in-flight [codex] Complete MCP enterprise managed auth support #1048 (oauth-service.ts) and Connection health checks: liveness, identity, and expired-credential visibility #1251 (add-account-modal.tsx, core-schema.ts).
  • Picker tiering is covered at the data/selector level; there's no DOM-level render test for the two-tier UI.
  • The ~45-line GC orchestration loop is duplicated between the local and cloud migrations deliberately — it's one-time migration code and the decision logic is already shared.
  • Follow-up candidates (not blocking): server-side kind filter on listClients (the mcp plugin currently re-implements the DCR check), and moving the legacy MCP-slug regex heuristic from the live deletion predicate to backfill-only.

A manual OAuth app registered from within an integration's connect dialog
now stamps that integration on origin_integration (kind stays manual),
mirroring how DCR clients already record the integration that requested
them. Thread it through the createClient wire contract (HTTP API + core
tool), persist it for both manual and DCR origins, and have
parseOAuthClientOrigin surface it on the manual summary so the picker can
match a bring-your-own app to its integration by recorded intent rather
than guessing by root domain.
…n domain

The Add-connection OAuth picker matched apps by registrable root domain
only, so a DCR-minted MCP client (e.g. one registered against
mcp.cloudflare.com) showed up when connecting the plain Cloudflare REST
API. DCR clients are plumbing, reused per authorization server, not apps
a user picks, so drop them from the picker entirely.

For the manual apps that remain, grade into honest tiers: tier 1 is a
match by recorded intent (registered from this integration's dialog) or
exact endpoint host; tier 2 is a root-domain-only near-miss, kept
separate so it is never silently presented as the integration's own app.
A new registration from the dialog stamps its integration so future
matches are exact, and the optimistic list mirrors the stamp immediately.
selectDcrClientsForIntegration exposes the excluded DCR clients for a
management surface.
Wire the modal to the new grouping: tier-1 exact/intent matches stay the
default-selectable list, root-domain near-misses render in a visually
separated, subdued "Other apps on this provider" group (selectable but
never auto-selected), and the auto-registered DCR clients for this
integration live in a collapsed "Auto-registered clients" section at the
bottom, deletable but not pickable. Resolving the chosen client now spans
every pickable tier so the near-miss and escape-hatch apps actually work.

Also drop the DCR connect path's dependency on the picker's slug list: the
slug is deterministic per authorization server since Part A, so
runDcrConnect no longer needs existingSlugs threaded in.
Factor the DCR-vs-manual classification of an oauth_client row (explicit
origin_kind or the legacy MCP-shaped heuristic) and the registrable-host
helpers out of oauth-service into oauth-gc, so the runtime reuse lookup and the
upcoming GC/backfill migrations share one source of truth for what counts as a
DCR client. parseOAuthClientOrigin now routes through isDcrClassifiedRow.

Also make the per-AS reuse pick deterministic: dcrCandidatesForIssuer now sorts
matching candidates oldest-first (created_at, slug tiebreak) before start picks
one, so which of several live duplicates sharing an (owner, issuer) gets reused
is stable across boots and storage backends instead of raw findMany order.

Add classifyOAuthClientGc (the conjunctive, fail-safe GC decision) and
registrableOriginOfUrl (the issuer backfill value) with unit tests covering the
decision matrix and the registrable-origin cases.
Add a libSQL boot data-migration (stamped through the existing data_migration
ledger) that deletes DCR-classified oauth_client rows with zero referencing
connections, and backfills origin_issuer for surviving DCR rows from the
registrable origin of their token_url so the per-AS reuse lookup can key on
them. The deletion predicate is conjunctive and fail-safe: a manual app, or a
DCR app still referenced by a connection, is always kept. It reads the (tiny)
oauth_client table and applies the shared classifyOAuthClientGc predicate in
process rather than re-encoding the DCR heuristic as SQL, keeping one source of
truth for a query that deletes user data. Idempotent: a second run is a no-op.

Register it as the last entry in the local data-migration registry. Tests cover
delete/keep/backfill, idempotency, the manual-never-deleted invariant, and the
empty-table no-op against the real SQLite schema.
Add the cloud counterpart of the local DCR oauth_client cleanup as a stamped
code migration run out-of-band by scripts/migrate.ts (with --dry-run and the
advisory lock the runner already provides). It reuses the same shared
classifyOAuthClientGc / registrableOriginOfUrl predicates the local path and the
runtime reuse lookup use, so both hosts delete and backfill by an identical
rule instead of a second Postgres-SQL encoding of the DCR heuristic and the
eTLD+1 origin logic. Registered unconditionally in cloudCodeMigrations (no R2
dependency). Tests drive it over a fake sql handle: delete/keep/backfill, the
dry-run plan, and the manual-never-deleted invariant.
Two integrations sharing a registrable root domain but differing by host (an
MCP integration on mcp.cloudflare.com vs the REST integration on
api.cloudflare.com) must not leak the MCP flow's auto-registered DCR client
into the REST integration's app picker, and reconnecting the MCP integration
must reuse the client rather than mint a duplicate.

The e2e proves the server half over the wire: DCR mints the client against the
MCP host, a second registration reuses it (no duplicate row), and listClients
projects it as a DCR client keeping its MCP endpoints. The react integration
test proves the picker half against the exact classifier the modal runs: the
DCR client is offered in no tier of the REST integration's picker, while a
same-root manual app still surfaces as a near-match and the DCR client stays
visible in its own management list.
hostOf returns undefined for URLs new URL() can't parse (a scheme-less
manifest-declared "oauth.cloudflare.com", or a DCR client built from
discovery metadata that skipped endpoint validation). The exact-host
comparison then read undefined === undefined as true, so an unrelated app
with an unparseable URL landed in tier 1 and became the picker's default.
Route every host/root comparison through a hostEq helper that only matches
when both sides parsed.

While here, memoize the picker grade so a fresh inline options object per
keystroke no longer re-parses every client with tldts, and rename the
client-side dcrClientSlug to optimisticDcrClientSlug: it is a host-only
placeholder the UI shows before the server recomputes the authoritative,
resource-aware slug, and sharing a name with the core function invited a
grep-edit in one package to silently miss the other.
decideDcrClientReuse fell back to candidates[0] when the request had no
resource and no resource-less candidate existed, so a resource-less flow
could reuse a client minted for a specific RFC 8707 resource (whose tokens
are bound to that resource). Only reuse a resource-less candidate now;
otherwise register a fresh resource-less client. The base dcr-<host> slug
can already be held by the first resource-scoped registration, and
createClient deletes any colliding (owner, slug) before writing, so dedupe
the fresh slug against existing candidates to avoid clobbering that client.
registrableHostname approximated eTLD+1 with a hardcoded 12-entry two-part
suffix set, so hosts under suffixes it didn't list collapsed too far:
accounts.example.co.in became co.in, colliding unrelated tenants in the GC
deletion predicate and the issuer backfill. Depend on tldts (the version
packages/react already pins) and use getDomain, falling back to the raw
lowercase hostname when it returns null (localhost, IPs, single-label
hosts). This matches how the react picker computes roots, so the reuse key
and the picker agree on the same URL. Also refresh the module header: the
predicates are one source of truth imported by three call sites, not three
copies kept in lockstep.
Both GC migrations (cloud code migration and libSQL boot migration) ran a
COUNT(*) against connection once per oauth_client row. Replace that N+1 with
a single GROUP BY (tenant, oauth_client_owner, oauth_client) pass loaded
into a Map before the loop. The cloud test fake now models the grouped
query shape instead of the per-row COUNT.
Swap the hand-rolled useState + Button + "+/-" glyph toggle for the shared
Collapsible/CollapsibleTrigger/CollapsibleContent with a rotating chevron,
matching the Advanced disclosure in mcp-install-card. Keeps the subdued
visual weight. Also drop the dead existingSlugs field from RunDcrConnectInput
(runDcrConnect never read it) and the stale existingSlugs: [] test args, so
readers stop thinking the connect path needs the picker's slug list.
Clarify that the column holds the discovered OIDC issuer for new DCR
registrations (real AS metadata, can differ from a token_url derivation) and
a derived registrable-origin for backfilled legacy rows (a cache of the pure
registrableOriginOfUrl).
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Console https://executor-preview-pr-1276.executor-e2e.workers.dev
MCP https://executor-preview-pr-1276.executor-e2e.workers.dev/mcp
Deployed commit 3e72fa3

Sign-in is Cloudflare Access (one-time PIN to an allowed email). The preview has its own database and encryption key; it is destroyed when this PR closes.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 3e72fa3 Commit Preview URL

Branch Preview URL
Jul 03 2026, 04:58 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 3, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 3e72fa3 Jul 03 2026, 04:59 AM

@pkg-pr-new

pkg-pr-new Bot commented Jul 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1276

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1276

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1276

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1276

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1276

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1276

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1276

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1276

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1276

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1276

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1276

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1276

executor

npm i https://pkg.pr.new/executor@1276

commit: 3e72fa3

@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the DCR OAuth client model with a cleaner per-AS reuse system, fixes picker contamination by completely hiding DCR clients from the app picker, and introduces one-time GC migrations to clean up legacy duplicate rows.

  • Per-AS reuse (Part A): registerDynamicClient now returns an existing client slug when one already exists for the same (owner, canonical issuer) pair, keyed on the real OIDC issuer discovered during probe. Slugs are deterministic (dcr-<host>) instead of numeric suffixes.
  • Picker redesign (Part B): DCR clients are excluded from selectClientsForEndpoints entirely; remaining manual apps are graded into tier-1 (recorded intent or exact host) and tier-2 (root-domain near-miss, shown in a collapsed subdued section). Registering a manual app from within an integration now stamps origin_integration.
  • GC + backfill (Part C): One-time migrations (local libSQL boot migration + cloud code migration) delete orphaned DCR rows and backfill origin_issuer on referenced survivors so the reuse lookup can find them without needing to re-probe.

Confidence Score: 5/5

Safe to merge. All existing connections resolve their client by stored (owner, slug) and are unaffected by the picker or GC changes. The GC only deletes orphaned rows with zero referencing connections, and the fail-safe predicate is conjunctive and well-tested.

The core reuse logic, picker redesign, and GC decision matrix are solid and covered by thorough tests. The one nuance — that backfilled legacy rows on subdomain-based authorization servers may not be reused immediately after the migration — results in at most one bounded duplicate per affected AS rather than an ongoing accumulation, and does not break any existing connection or security boundary.

The dcrCandidatesForIssuer function in packages/core/sdk/src/oauth-service.ts around the rowIssuer !== null branch deserves a second look together with the backfill logic in both migration files, specifically for the case where token_url is on a subdomain of the registrable root domain.

Important Files Changed

Filename Overview
packages/core/sdk/src/oauth-service.ts Core DCR reuse logic: decideDcrClientReuse and dcrCandidatesForIssuer implement per-AS client lookup; parseOAuthClientOrigin now delegates DCR classification to the shared isDcrClassifiedRow predicate. The backfill/issuer matching gap noted in comments is the main concern.
packages/core/sdk/src/oauth-gc.ts New shared GC classification predicates (isDcrClassifiedRow, classifyOAuthClientGc) and URL helpers (registrableOriginOfUrl, canonicalIssuerUrl). Single source of truth imported by both migrations and the runtime reuse lookup. Logic is clear and well-documented.
packages/core/sdk/src/sqlite-oauth-client-gc-migration.ts Local libSQL boot migration: reads oauth_client and connection tables, applies GC decisions in-process, backfills origin_issuer from token_url. Wrapped in explicit BEGIN/COMMIT with ROLLBACK on error; idempotent via the data-migration ledger.
apps/cloud/scripts/code-migrations/gc-dead-dcr-oauth-clients.ts Cloud code migration counterpart: one grouped COUNT query replaces per-row lookups, shares classification predicates with the local migration. Dry-run support and advisory locking are delegated to the runner.
packages/react/src/plugins/use-effective-oauth-client.tsx Picker split into three tiers: selectClientsForEndpoints filters DCR clients first, then grades manual apps by recorded intent, exact host, and root domain. selectDcrClientsForIntegration powers the new management surface.
packages/react/src/components/add-account-modal.tsx Modal now renders near-match tier 2 and a collapsed 'Auto-registered clients' section. existingSlugs widened to all clientSummaries. chosenClient search spans all three pickable tiers.
packages/react/src/components/oauth-client-form.tsx Adds integrationSlug prop to stamp recorded intent on new manual registrations; discoveredIssuer threads the probed issuer into DCR registration. Edit path always clears originIntegration — flagged in the prior review thread.
packages/core/sdk/src/oauth-gc.test.ts Thorough decision-matrix tests including multi-label PSL domains, idempotency, and boundary cases. No post-backfill reuse scenario tested.
packages/core/sdk/src/oauth-register-dynamic.test.ts Comprehensive reuse matrix: same AS/different owner, resource scoping, legacy null-issuer fallback, resource-less never borrows resource-scoped client. Legacy reuse test uses local IP server, leaving subdomain mismatch case untested.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client as Connect Flow
    participant Service as oauth-service.ts
    participant DB as oauth_client table
    participant AS as Authorization Server

    note over DB: After GC migration: legacy rows have origin_issuer = registrableOrigin(token_url)

    Client->>Service: registerDynamicClient(owner, issuer?, registrationEndpoint, resource)
    Service->>AS: probe OIDC discovery
    AS-->>Service: "issuer = https://oauth.cloudflare.com"
    Service->>Service: canonicalDcrIssuer returns https://oauth.cloudflare.com

    Service->>DB: findMany(owner) all DCR candidates
    DB-->>Service: "slug=cloudflare-mcp, origin_issuer=https://cloudflare.com"

    Service->>Service: dcrIssuerMatches(https://cloudflare.com, https://oauth.cloudflare.com)
    note right of Service: rowIssuer non-null skips token-host fallback. Neither dcrIssuerMatches arm covers registrable-root vs subdomain.

    alt issuer mismatch - subdomain AS with backfilled legacy row
        Service->>Service: no candidate found
        Service->>AS: POST /register new DCR client
        AS-->>Service: client_id
        Service->>DB: "INSERT dcr-oauth-cloudflare-com with origin_issuer=https://oauth.cloudflare.com"
        note over DB: cloudflare-mcp AND dcr-oauth-cloudflare-com now co-exist
    else issuer matches - localhost or exact origin
        Service->>Service: existingSlug found return immediately
        note over Service: No registration call, no duplicate
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client as Connect Flow
    participant Service as oauth-service.ts
    participant DB as oauth_client table
    participant AS as Authorization Server

    note over DB: After GC migration: legacy rows have origin_issuer = registrableOrigin(token_url)

    Client->>Service: registerDynamicClient(owner, issuer?, registrationEndpoint, resource)
    Service->>AS: probe OIDC discovery
    AS-->>Service: "issuer = https://oauth.cloudflare.com"
    Service->>Service: canonicalDcrIssuer returns https://oauth.cloudflare.com

    Service->>DB: findMany(owner) all DCR candidates
    DB-->>Service: "slug=cloudflare-mcp, origin_issuer=https://cloudflare.com"

    Service->>Service: dcrIssuerMatches(https://cloudflare.com, https://oauth.cloudflare.com)
    note right of Service: rowIssuer non-null skips token-host fallback. Neither dcrIssuerMatches arm covers registrable-root vs subdomain.

    alt issuer mismatch - subdomain AS with backfilled legacy row
        Service->>Service: no candidate found
        Service->>AS: POST /register new DCR client
        AS-->>Service: client_id
        Service->>DB: "INSERT dcr-oauth-cloudflare-com with origin_issuer=https://oauth.cloudflare.com"
        note over DB: cloudflare-mcp AND dcr-oauth-cloudflare-com now co-exist
    else issuer matches - localhost or exact origin
        Service->>Service: existingSlug found return immediately
        note over Service: No registration call, no duplicate
    end
Loading

Reviews (2): Last reviewed commit: "Escape the NUL separator so the cloud GC..." | Re-trigger Greptile

Comment on lines 307 to 309
clientId: clientId.trim(),
clientSecret: clientSecret.trim(),
resource,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Editing an app silently clears its recorded-intent stamp

originIntegration: fixedSlug ? null : (integrationSlug ?? null) means that any edit operation (changing client ID, secret, name, etc.) replaces origin_integration with null, stripping the very intent stamp this PR relies on for tier-1 picker matching. An app registered from the Cloudflare MCP dialog with origin_integration = "cloudflare_mcp" would drop to tier-2 (root-domain near-match) after the user's first edit — even if no endpoint URLs changed. The OAuthClientFormPrefill interface doesn't thread originIntegration through, so the existing stamp is unrecoverable in the edit path.

Comment on lines +132 to +135
yield* execute(client, "BEGIN");
return yield* applyAll.pipe(
Effect.tapError(() => execute(client, "ROLLBACK").pipe(Effect.ignore)),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Transaction has no rollback on fiber interruption

Fiber interruption (process kill during boot) does not trigger tapError, leaving an open SQLite transaction that relies on connection-close for implicit rollback. Adding Effect.onInterrupt makes the boundary explicit.

Suggested change
yield* execute(client, "BEGIN");
return yield* applyAll.pipe(
Effect.tapError(() => execute(client, "ROLLBACK").pipe(Effect.ignore)),
);
yield* execute(client, "BEGIN");
return yield* applyAll.pipe(
Effect.tapError(() => execute(client, "ROLLBACK").pipe(Effect.ignore)),
Effect.onInterrupt(() => execute(client, "ROLLBACK").pipe(Effect.ignore)),
);

The reference-count map key used literal 0x00 bytes as its separator, which made git (and GitHub's diff view) treat the whole file as binary. Use the \u0000 escape instead — same runtime string, plain-text source. The local sqlite migration already used a printable separator.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't configure oauth mcp which used to work

1 participant