A multi-org user loading /<orgB> while their session cookie still points at orgA saw orgA's slug flash into the copyable `npx add-mcp .../<slug>/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.
Unified · Split
| @@ -275,6 +275,15 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) { · 274 unmodified lines | ||
| 275 | 275 | // which is the same origin, so the key never changes and nothing remounts. |
| 276 | 276 | const connection = ssrOrigin ? ({ kind: "http", origin: ssrOrigin } as const) : undefined; |
| 277 | 277 | const activeSlug = auth.organization.slug; |
| 278 | + // The org context's slug feeds the connect card's `/<slug>/mcp` install URL. | |
| 279 | + // Prefer the URL's slug over the session's: on first paint `auth.organization` | |
| 280 | + // comes from the SSR auth-hint (the COOKIE's org), so a multi-org user viewing | |
| 281 | + // /<orgB> while their cookie still points at orgA would briefly render orgA's | |
| 282 | + // slug in the copyable URL before /account/me (URL-scoped) corrects it. The | |
| 283 | + // URL slug is the actual request scope and is correct on the very first paint, | |
| 284 | + // so sourcing it from there removes that flash. Falls back to the session slug | |
| 285 | + // on a bare URL (which OrgSlugGate is about to canonicalize onto it anyway). | |
| 286 | + const scopeSlug = urlOrgSlug ?? activeSlug; | |
| 278 | 287 | |
| 279 | 288 | return ( |
| 280 | 289 | <AutumnProvider pathPrefix="/api/billing"> |
| @@ -284,7 +293,7 @@ function AuthGate({ ssrOrigin }: { ssrOrigin: string | null }) { · 3 unmodified lines | ||
| 284 | 293 | <ExecutorPluginsProvider plugins={clientPlugins}> |
| 285 | 294 | <OrganizationProvider |
| 286 | 295 | organizationId={auth.organization.id} |
| 287 | − organizationSlug={activeSlug} | |
| 296 | + organizationSlug={scopeSlug} | |
| 288 | 297 | > |
| 289 | 298 | {/* The org header scopes every request to the URL's org, so |
| 290 | 299 | reaching here means the caller is a member of `activeSlug` |