Recipes

OAuth outage handling fix: 503 + Retry-After instead of sign-in failures

Discovered from README cross-links · today

Security, Science & HardwareSoftware & DevOps

Stop telling users to reconnect during provider outages; return retryable 503s.

What they did

The author reworked how the Gram gateway reports OAuth failures so that transient outages are no longer surfaced as sign-in problems. Previously, a refresh that failed because a provider was down (remote_session_unavailable, upstream_error, rate_limited, unreachable) caused the dial to go anonymous, the upstream to return 401, and the UI to say 'not connected for this user' with a Connect button. The fix introduces a new `unavailable` connection status, returns HTTP 503 with `Retry-After` and a `temporarily_unavailable` body across the token endpoint, MCP, discovery, register, shared token and browser sign-in routes, and keeps `invalid_grant` only for genuine not-found or refused cases. They added an allowlist for database outages (connection failures, SQLSTATE 08/53, 57P01-57P03, serialization failures, deadlocks, network errors, pool timeouts) applied to OAuthErrHandle, MCPErrHandle, BrowserErrHandle and a new AuthErrHandle, while syntax errors and missing tables stay 500. The connect page now reads a per-grant marker in the shared cache written on refresh failure, lasting 15 minutes, and shows 'Unavailable · try again later' with Reconnect instead of auto-closing. Replay reservations were made atomic under a per-request owner with compare-and-delete release, and refresh POSTs are paced ~30s (or provider Retry-After up to 5 min) inside the single-flight lease to avoid reuse-detection revocation.

How it went

The change is a detailed, well-scoped PR with a full before/after table, explicit reversal notes (the tunnel 503 reverses an earlier deliberate 404), alerting rationale (external X-Gram-Proxy-Response tag for customer/provider causes only), and a documented list of known limits. It includes tests implied by the allowlist and replay semantics. Platform and Admin MCP needed no changes because the new status flows through the existing connection_status field.

Worth knowing

Known limits are documented: the connect page stays Unavailable after recovery until a tool call or manual Refresh, up to 15 minutes; if a client gives up before a slow refresh fails, no marker is recorded; and the TS SDK's auth() called directly with no cached discovery during a database outage can fall back to root metadata and return REDIRECT. Refresh handling for unexpected 4xx is out of scope (AIM-859). The pacing depends on Redis and fails open if it is down.

Wiring

The change threads through the gateway's error-handling layer: OAuthErrHandle, MCPErrHandle, BrowserErrHandle and a new AuthErrHandle for discovery and registration classify errors, with a database-outage allowlist deciding 503 vs 500. The token endpoint returns 503 with a temporarily_unavailable body and no WWW-Authenticate; the connect page reads a per-grant marker from the shared cache written on refresh failure, cleared by successful refresh or reconnect. Replay reservations use atomic claim under a per-request owner with compare-and-delete release, and refresh POSTs are paced via Redis inside the single-flight lease. The tunnel 503 is tagged X-Gram-Proxy-Response: 1 so on-call alerts ignore customer and provider causes.

Try it yourself

In this repo, find where OAuth refresh failures and provider outages are mapped to user-facing errors and HTTP status codes. Introduce a distinct 'unavailable' outcome for transient provider and database outages: return 503 with Retry-After and a temporarily_unavailable body on token, MCP, discovery, register and browser sign-in routes, and reserve invalid_grant for genuine not-found or refused cases. Add a database-outage allowlist (connection failures, SQLSTATE 08/53, 57P01-57P03, serialization failures, deadlocks, network errors, pool timeouts) scoped to the OAuth, MCP, Browser and a new Auth error handler, leaving syntax errors and missing tables as 500. Update the connect page to read a per-grant outage marker from the shared cache (15 min TTL) and show 'Unavailable · try again later' with Reconnect instead of auto-closing. Make replay reservations atomic with compare-and-delete release, and pace refresh POSTs ~30s or the provider's Retry-After inside the single-flight lease. Add tests for each row of the before/after table.

Source

Tools in this stack