From 405e3c0425cdb198d8f2be1a1f0da6bc8562a224 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan Date: Sat, 13 Jun 2026 14:20:06 -0700 Subject: [PATCH] Connect card: pin the MCP install URL to the URL's org, not the cookie's MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A multi-org user loading / while their session cookie still points at orgA saw orgA's slug flash into the copyable `npx add-mcp ...//mcp` box before /account/me resolved. On first paint `auth.organization` comes from the SSR auth-hint, which is scoped to the COOKIE's org — but the page is scoped to the URL's org. Seed OrganizationProvider's slug (which feeds the connect card via useOrganizationSlug) from the URL's `{-$orgSlug}` param, falling back to the session slug on a bare URL (which OrgSlugGate canonicalizes onto it anyway). The URL slug is the actual request scope and is correct on the very first paint, so the wrong slug never appears. OrgSlugGate keeps using the session slug for canonicalization; only the install-URL context changes. useOrganizationSlug is consumed solely by the connect card, so there's no other blast radius. --- apps/cloud/src/routes/__root.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/cloud/src/routes/__root.tsx b/apps/cloud/src/routes/__root.tsx index ce57b2709..e98e6c8e8 100644 --- a/apps/cloud/src/routes/__root.tsx +++ b/apps/cloud/src/routes/__root.tsx @@ -275,6 +275,15 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) { // which is the same origin, so the key never changes and nothing remounts. const connection = ssrOrigin ? ({ kind: "http", origin: ssrOrigin } as const) : undefined; const activeSlug = auth.organization.slug; + // The org context's slug feeds the connect card's `//mcp` install URL. + // Prefer the URL's slug over the session's: on first paint `auth.organization` + // comes from the SSR auth-hint (the COOKIE's org), so a multi-org user viewing + // / while their cookie still points at orgA would briefly render orgA's + // slug in the copyable URL before /account/me (URL-scoped) corrects it. The + // URL slug is the actual request scope and is correct on the very first paint, + // so sourcing it from there removes that flash. Falls back to the session slug + // on a bare URL (which OrgSlugGate is about to canonicalize onto it anyway). + const scopeSlug = urlOrgSlug ?? activeSlug; return ( @@ -284,7 +293,7 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) { {/* The org header scopes every request to the URL's org, so reaching here means the caller is a member of `activeSlug`