Skip to content

Reuse DCR client per integration instead of minting duplicates (part of #1120)#1142

Open
RhysSullivan wants to merge 1 commit into
fix/1120-oauth-cross-integration-leakfrom
fix/1120-dedupe-dcr-clients
Open

Reuse DCR client per integration instead of minting duplicates (part of #1120)#1142
RhysSullivan wants to merge 1 commit into
fix/1120-oauth-cross-integration-leakfrom
fix/1120-dedupe-dcr-clients

Conversation

@RhysSullivan

Copy link
Copy Markdown
Owner

Part of #1120 (2 of 4). Stacked on #1139 (the cross-integration picker fix). Review/merge that first; this PR is based on its branch so the diff here is only the dedup change.

Problem

runDcrConnect minted a fresh oauth_client on every connect, so repeated connects to the same integration piled up "Datadog", "Datadog 2", ... "Datadog 10".

Fix

A fresh probe of the same server yields the same endpoints, so an existing DCR client is still valid. Before minting, reuse an existing client for the same integration + owner + discovered token host (matched via origin.integration, added in the PR this stacks on) and just re-run the authorize/token flow with it.

Tests

Adds unit guards in add-account-modal: reuses a client minted for this integration, and does not reuse one belonging to a different integration (lint 0/0, typecheck 41/41, add-account-modal 23/23).

… duplicates

runDcrConnect minted a fresh oauth_client on every connect, so repeated
connects to the same integration piled up "Datadog", "Datadog 2", ...
"Datadog 10". A fresh probe of the same server yields the same endpoints,
so an existing DCR client is still valid.

Before minting, reuse an existing client for the same integration +
owner + discovered token host (matched via origin.integration, added in
the cross-integration fix this stacks on) and just re-run the
authorize/token flow with it.

Adds unit guards in add-account-modal: reuses a client minted for this
integration, and does not reuse one belonging to a different integration.

Stacked on the cross-integration picker fix (origin.integration plumbing).
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 26, 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 47bb747 Commit Preview URL

Branch Preview URL
Jun 26 2026, 05:41 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 26, 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 47bb747 Jun 26 2026, 05:41 AM

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare preview

Console https://executor-preview-pr-1142.executor-e2e.workers.dev
MCP https://executor-preview-pr-1142.executor-e2e.workers.dev/mcp
Deployed commit 47bb747

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.

@pkg-pr-new

pkg-pr-new Bot commented Jun 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

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

@executor-js/config

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

@executor-js/execution

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

@executor-js/sdk

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

@executor-js/codemode-core

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

@executor-js/runtime-quickjs

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

@executor-js/plugin-file-secrets

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

@executor-js/plugin-graphql

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

@executor-js/plugin-keychain

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

@executor-js/plugin-mcp

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

@executor-js/plugin-onepassword

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

@executor-js/plugin-openapi

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

executor

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

commit: 47bb747

@greptile-apps

greptile-apps Bot commented Jun 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the existingSlugs parameter in runDcrConnect with existingClients (the full OAuthClientOption[]) so the function can both avoid slug collisions and detect an existing DCR client for the same integration — skipping re-registration and fixing the "Datadog, Datadog 2, … Datadog 10" accumulation bug.

  • The reuse match keys on owner, origin.integration, and the registrable root domain of tokenUrl (via getRootDomain), and short-circuits to deps.start with the existing slug when a match is found.
  • Two new unit tests guard the positive case (reuse within same integration) and the negative case (no cross-integration reuse).

Confidence Score: 3/5

The reuse check is gated behind a guard that fires before it, so existing valid clients are silently bypassed when a server stops advertising its DCR endpoint.

The core dedup logic works on the happy path, but the reuse guard is placed after an early-return check unrelated to reuse — a server dropping its registration endpoint makes the reuse branch unreachable while the existing client sits unused.

packages/react/src/components/add-account-modal.tsx — specifically the ordering of the registrationEndpoint guard vs. the reusable-client search inside runDcrConnect.

Important Files Changed

Filename Overview
packages/react/src/components/add-account-modal.tsx Adds DCR client reuse logic gated behind the registrationEndpoint guard, making it unreachable when a server stops advertising DCR metadata; also has a subtle undefined===undefined match risk.
packages/react/src/components/add-account-modal.test.ts Updates existingSlugs to existingClients and adds two regression tests covering same-integration reuse and cross-integration isolation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[runDcrConnect called] --> B[probe discoveryUrl]
    B --> C{probe === null?}
    C -- yes --> D[return fallback: probe-failed]
    C -- no --> E{registrationEndpoint?}
    E -- no --> F[return fallback: no-registration-endpoint]
    E -- yes --> G[compute probedTokenRoot from probe.tokenUrl]
    G --> H{existing client with same owner + integration + token root domain?}
    H -- yes --> I[deps.start with existing slug]
    I --> J[return started]
    H -- no --> K[uniqueClientSlug for new name]
    K --> L[deps.register new DCR client]
    L --> M{minted === null?}
    M -- yes --> N[return fallback: probe-failed]
    M -- no --> O[deps.start with new slug]
    O --> J
    style F fill:#ffcccc,stroke:#cc0000
    style D fill:#ffcccc,stroke:#cc0000
    style N fill:#ffcccc,stroke:#cc0000
    style J fill:#ccffcc,stroke:#00aa00
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"}}}%%
flowchart TD
    A[runDcrConnect called] --> B[probe discoveryUrl]
    B --> C{probe === null?}
    C -- yes --> D[return fallback: probe-failed]
    C -- no --> E{registrationEndpoint?}
    E -- no --> F[return fallback: no-registration-endpoint]
    E -- yes --> G[compute probedTokenRoot from probe.tokenUrl]
    G --> H{existing client with same owner + integration + token root domain?}
    H -- yes --> I[deps.start with existing slug]
    I --> J[return started]
    H -- no --> K[uniqueClientSlug for new name]
    K --> L[deps.register new DCR client]
    L --> M{minted === null?}
    M -- yes --> N[return fallback: probe-failed]
    M -- no --> O[deps.start with new slug]
    O --> J
    style F fill:#ffcccc,stroke:#cc0000
    style D fill:#ffcccc,stroke:#cc0000
    style N fill:#ffcccc,stroke:#cc0000
    style J fill:#ccffcc,stroke:#00aa00
Loading

Reviews (1): Last reviewed commit: "Fix #1120: reuse the DCR client for an i..." | Re-trigger Greptile

Comment on lines 507 to +526
const registrationEndpoint = probe.registrationEndpoint;
if (!registrationEndpoint) return { kind: "fallback", reason: "no-registration-endpoint" };

const slug = uniqueClientSlug(input.integrationName, input.existingSlugs);
// Reuse a client already minted for THIS integration (same owner, same
// discovered token host) instead of minting a duplicate every connect — the
// cause of "Datadog", "Datadog 2", … "Datadog 10" piling up. A fresh probe of
// the same server yields the same endpoints, so an existing DCR client is
// still valid; we re-run only the authorize/token flow with it.
const probedTokenRoot = getRootDomain(probe.tokenUrl);
const reusable = input.existingClients.find(
(app) =>
app.owner === input.owner &&
app.origin?.integration != null &&
String(app.origin.integration) === String(input.integration) &&
getRootDomain(app.tokenUrl) === probedTokenRoot,
);
if (reusable) {
deps.start({ client: reusable.slug, owner: input.owner });
return { kind: "started" };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Reuse check unreachable when server stops advertising its DCR endpoint

The if (!registrationEndpoint) return { kind: "fallback", reason: "no-registration-endpoint" } guard fires at line 508, before the reusable-client search is reached. If a server probe succeeds but the server temporarily (or permanently) drops its registration_endpoint from discovery metadata, any existing valid DCR client for that integration is ignored and the user hits "Automatic setup unavailable". The reuse path doesn't use registrationEndpoint at all — it only needs probe.tokenUrl — so the check should run after the reuse attempt, or the two paths should be split: try reuse first, only then require registrationEndpoint before minting.

Comment on lines +515 to +522
const probedTokenRoot = getRootDomain(probe.tokenUrl);
const reusable = input.existingClients.find(
(app) =>
app.owner === input.owner &&
app.origin?.integration != null &&
String(app.origin.integration) === String(input.integration) &&
getRootDomain(app.tokenUrl) === probedTokenRoot,
);

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 getRootDomain returns string | undefined. When probe.tokenUrl is unparseable, probedTokenRoot is undefined, and any existing client whose tokenUrl is also unparseable will satisfy undefined === undefined, producing a spurious match. Add an explicit guard so that an undefined domain never matches anything.

Suggested change
const probedTokenRoot = getRootDomain(probe.tokenUrl);
const reusable = input.existingClients.find(
(app) =>
app.owner === input.owner &&
app.origin?.integration != null &&
String(app.origin.integration) === String(input.integration) &&
getRootDomain(app.tokenUrl) === probedTokenRoot,
);
const probedTokenRoot = getRootDomain(probe.tokenUrl);
const reusable =
probedTokenRoot != null
? input.existingClients.find(
(app) =>
app.owner === input.owner &&
app.origin?.integration != null &&
String(app.origin.integration) === String(input.integration) &&
getRootDomain(app.tokenUrl) === probedTokenRoot,
)
: undefined;

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.

1 participant