Skip to content

fix(api): a removed tenant's refusal carries no CORS headers, so a browser stream re-dials forever #637

Description

@EricAndrechek

Area: api · streaming — footgun · found via #611's follow-ups (PR body)

Expected: a browser client whose tenant has been removed reads the 404 and stops, which is what the SDK is built to do — it stops a stream on a 404 and retries a 503 (clients/ts/src/stream/sse.ts).

Actual: over a nested settings directory with no 0 folder, the refusal carries no CORS headers, so the browser never hands the status to the SDK. corsOrigins (internal/api/router.go:443-462) answers a tenant route from the request's tenant only when the registry still holds it; a tenant it does not hold falls back to tenant.Default, and with no 0 folder served that lookup fails too and the function returns nil. corsMiddleware then writes no Access-Control-Allow-Origin, the fetch rejects, and the transport reports a network error. The SDK treats that as a drop and re-dials — forever, since the tenant is gone for good.

Impact: every browser page open on a removed tenant becomes a permanent re-dial loop against a server that will never serve it again, and the operator sees traffic with no way to tell it from a flapping network. The same fallback makes the 400/404/503 of tenant resolution unreadable to a browser for every unknown tenant, not only a removed one.

#611 names two candidate shapes and picks neither, because the choice is Eric's: (a) whatever fronts WaveHouse turns away an unknown subdomain and answers with CORS headers, the preflight included — which makes this a deployment requirement to document rather than engine work; or (b) the evicted stream sends a final event before it closes, which that connection's own CORS does let the browser read. Distinct from #471, which is a proxy-terminated preflight rejecting Last-Event-ID; this one is WaveHouse's own refusal, and the fix is on the server side.

Related: #611, #600 (refusals read tenant 0's list), #583 (story 3), #471, #469


From #611's "Follow-ups" section (taitelee), flagged there as a design question for Eric and no story owns it; validated by code-read against 93d80198 on 2026-09-25. Filed by the pm-triage routine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/apiHTTP handlers, routing, middlewarearea/streamingSSE / live-query delivery path (/v1/stream)area/tenantTenant id, header resolution, per-tenant settings (internal/tenant)bugSomething isn't working

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions