Skip to content

CL-6394: route OAuth connects through the tenant-scoped mount; one shared persist-and-seed sequence - #149

Merged
TheGreatAxios merged 3 commits into
mainfrom
cl-6394-oauth-wiring
Aug 20, 2026
Merged

TheGreatAxios merged 3 commits into
mainfrom
cl-6394-oauth-wiring

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Fixes the hosted GitHub one-click connect crash traced in CL-6394: the only UI entry point targeted /api/onboarding/oauth/github/start, whose mount dispatches only openrouter/huggingface — a github callback fell into inference-only seeding and hit CATALOG_SEEDS["github"] (undefined) AFTER a successful token exchange, while the correct tenant-scoped mount from #136 had zero frontend consumers.

What changed

  • Start URLs: oauthStartHref now emits /api/tenants/:id/connections/oauth/:connector/start; both non-onboarding connect surfaces (settings Connections cards, plugins gallery panel) ride it. The onboarding wizard keeps its own mount for its first-login OpenRouter/Hugging Face flow only.
  • Onboarding mount closed: its registry names exactly its two providers (no CONNECTOR_REGISTRY spread), so oauth/github/start there is a loud 404; persistence/recovery/pending-seed narrow the connector id explicitly instead of as SupportedCredentialProvider.
  • Typed factory parameter: createOAuthConnectRoutes<E extends AppEnv> threads the env through connectCredential/afterConnected, so createTenantConnectCredential reads tenant/principal off Context<TenantEnv> with no cast.
  • One shared persistConnectorCredential (@workbench/connections/persist-credential) replaces the three parallel ensureProvider→ensureCredential→seedCatalog copies (connections /complete, onboarding testAndPersistCredential, tenant OAuth connect). Only inference providers reach seedCatalog; it now takes existingCredentialId so the caller's single ensureCredential stays the only credential write, and CL-6351's hasCompletionCapableModel guidance still flows back through /complete. Credential metadata (HF token expiry) now survives the tenant OAuth path too.
  • Second latent bug caught by the new red/green test: ConnectStatePayload required a non-empty codeVerifier, but GitHub's non-PKCE flow seals an empty one — every github callback would have been reported state_expired even with the routing fixed. Now accepts the empty string.
  • Gallery affordance: not-connected plugin rows use the same single Connect verb as the MCP preset rows instead of a bare "+" glyph (owner rule: actions look actionable).
  • Trivia: dead .app-mark/.app-wordmark CSS deleted; .env.example callback-URL notes updated (stale "not yet mounted" note dropped).

Red/green

packages/connections/test/github-oauth-connect.test.ts drives the exact start URL the UI emits through start → callback with a fake GitHub token exchange server: credential persisted, no catalog seed touched. oauth-start-href.test.ts pins the same literal on the UI side; the onboarding suite pins the github 404.

Notes

  • Hugging Face / OpenRouter settings- and plugins-surface connects moved to the tenant mount with GitHub. GitHub matches redirect URIs by prefix (register <BASE_URL>/api/tenants/); OpenRouter accepts any callback_url. Hugging Face registers exact redirect URIs — the tenant-scoped callback shape may need its own registration; flagged in .env.example and worth a follow-up check against a real HF app.
  • Pre-existing at origin/main, not touched here: 4 flaky apps/web toast tests (fail under the full parallel suite, pass in isolation; identical with this branch's changes stashed), the apps/web sonner tsc resolution error, and the workflow-catalog non-null-assertion lint error.

…OAuth mount

The chain CL-6394 traced: the plugins gallery's Connect link targeted
/api/onboarding/oauth/github/start, whose mount dispatches only
openrouter/huggingface and crashed a github callback after a successful
token exchange (CATALOG_SEEDS["github"] is undefined). These tests pin
the fixed contract: the UI's start URL targets the tenant-scoped
connections/oauth mount, a github callback through that exact URL
persists the exchanged token (fake exchange server) without ever
touching the catalog seeds, the onboarding mount answers a loud 404 for
github, and the shared persist-and-seed helper decides catalog seeding
in one place.
…ount

CL-6394: the hosted GitHub one-click connect crashed in its callback —
the UI targeted onboarding's OAuth mount, which only knows
openrouter/huggingface, while the correct tenant-scoped
connections/oauth mount had zero frontend consumers.

- oauthStartHref now emits /api/tenants/:id/connections/oauth/:connector/start;
  the settings Connections cards and the plugins gallery both ride it.
  Onboarding's own mount keeps only its first-login OpenRouter/Hugging
  Face flow: its registry no longer spreads CONNECTOR_REGISTRY, so any
  other connector 404s loudly, and its persistence narrows the connector
  id instead of casting it.
- createOAuthConnectRoutes takes a typed env parameter, so the
  tenant-scoped connectCredential reads tenant/principal off
  Context<TenantEnv> with no cast.
- One shared persistConnectorCredential replaces the three parallel
  ensureProvider -> ensureCredential -> seedCatalog copies (connections
  /complete, onboarding's testAndPersistCredential, the tenant OAuth
  connect); only inference providers ever reach seedCatalog, and
  seedCatalog now accepts the already-planted credential so the one
  ensureCredential write stays the only one. Credential metadata (token
  expiry) now survives the tenant OAuth path too.
- ConnectStatePayload accepts an empty codeVerifier — the non-PKCE
  GitHub flow seals one, and the old 'string > 0' silently expired every
  such callback.
- The plugins gallery's not-connected rows use the same single Connect
  verb as the MCP preset rows instead of a bare plus glyph.
.env.example now names the tenant-scoped github/huggingface callback
shapes the connect surfaces actually drive (CL-6394) and drops the
stale not-yet-mounted note. Also removes the .app-mark/.app-wordmark
rules nothing renders anymore.
@TheGreatAxios
TheGreatAxios merged commit 1fed4b7 into main Aug 20, 2026
0 of 2 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-6394-oauth-wiring branch August 25, 2026 15:29
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