Skip to content

Fix ensureCredential dropping a verified oauth_token reconnect - #489

Merged
TheGreatAxios merged 2 commits into
mainfrom
cl-7236-ensure-credential-oauth-drop
Aug 30, 2026
Merged

TheGreatAxios merged 2 commits into
mainfrom
cl-7236-ensure-credential-oauth-drop

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Summary

ensureCredential's rotate-on-409-conflict check for oauth_token credentials only looked at the existing row's status. The interactive MCP OAuth reconnect route (packages/connections/src/mcp-oauth-routes.ts) already passes verified: true after completing a fresh, probe-confirmed OAuth exchange -- but that signal was silently ignored for oauth_token, so reconnecting an integration whose stored credential was still active (not yet expired) dropped the freshly minted token entirely: zero PATCH calls, and the stale row's id returned as if the reconnect had worked.

Fix: rotate whenever the existing row is stale (status !== "active", unchanged) or the caller marks the token verified -- mirroring how the api_key branch already uses verified. A plain workbench seed never sets verified on an oauth_token credential, so a routine unverified re-seed of a still-active row keeps skipping exactly as before.

Self-review (Greybeard/Critique unavailable this session -- see note)

  1. Every non-active status still rotates, active now rotates too when verified. The four credential statuses (active, expired, revoked, error) are covered: any non-active status rotates unconditionally (unchanged from before), and active now also rotates when args.verified === true.
  2. A failed or partial exchange cannot overwrite a working token. Every caller that sets verified: true for an oauth_token credential has already proven the secret before calling ensureCredential: the MCP OAuth route only reaches it after probe(...) confirms the exchanged token actually works (redirecting with an error first otherwise), and the generic persistConnectorCredential path (packages/connections/src/persist-credential.ts) documents the same invariant -- "the credential is already proven by the OAuth exchange itself."
  3. The stale-id-returned-as-success path is eliminated for the bug case, not just narrowed. The only remaining skip-and-return-existing-id branch for oauth_token is an active row with verified unset or false -- the intentional, correct "idempotent re-seed with an unchanged token" case the acceptance criteria call out to preserve. Every "a genuinely new, verified token arrived" case now rotates.

Note

Greybeard and Critique subagent review were unavailable for this change (session-wide concurrent-subagent limit hit repeatedly); the self-review above substitutes.

Test plan

  • WORKBENCH_CHECK_CONCURRENCY=2 WORKBENCH_CHECK_SINCE=origin/main bun run typecheck -- clean
  • WORKBENCH_CHECK_CONCURRENCY=2 WORKBENCH_CHECK_SINCE=origin/main bun run test (narrowed per-package runner; scripts/*.test.ts run separately without the concurrency env var, which otherwise collides with its own concurrency-default assertion) -- all packages touched by this change pass; @corbits/chat-ui and @corbits/routines failures under the capped parallel run were confirmed to be resource-contention flakes unrelated to this change (both pass standalone, 817/817 and 267/267)
  • bun run lint -- 0 errors (8 pre-existing warnings, none in touched files)
  • bun run check:structural -- clean
  • New regression test added: packages/hub-client/test/seed.test.ts -- "a name conflict on a verified oauth_token reconnect rotates the still-active row", confirmed red against the pre-fix code (0 PATCH calls) and green after the fix

Reproduces CL-7236: an interactive MCP OAuth reconnect passes
credentialVerified: true after completing a fresh exchange, but a name
conflict against a still-active existing row currently skips rotation
entirely -- zero PATCH calls, and the stale credential id is returned
as if the reconnect succeeded.
…nect

The oauth_token rotate-on-conflict check only looked at the existing
row's status, so a caller passing verified: true (an interactive MCP
OAuth reconnect that just completed a fresh, probe-confirmed exchange)
had that signal silently ignored whenever the stored row hadn't yet
gone stale. The freshly minted token was dropped and the stale
credential id returned as if the reconnect had worked.

Rotate whenever the row is stale OR the caller marks the token
verified, mirroring how the api_key branch already uses verified.
@TheGreatAxios
TheGreatAxios merged commit 8dc571d into main Aug 30, 2026
5 checks passed
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