Skip to content

Supabase incidents: what a client can actually do - a resilience reference

The platform fails in a small number of recurring ways. Each class below has a mechanism, a client-visible signature, and a workaround tier - and each claim is marked as measured in the lab (with the number) or documented-only. The implementation of every workaround here is the companion guide, the resilience runbook; the RPO/RTO/monthly-cost axis for the durability tiers is the third doc, the DR tiers reference.

Provenance. Measured numbers come from the supabase-lab pvlab harness - experiments/edge-resilience (2026-08-15/16, W21/W25/W26 2026-08-17) and experiments/platform-downtime (2026-08-04, n=1 per operation, one Micro project in ap-southeast-1); the cutover drill ran cross-region ap-southeast-2 -> ap-southeast-1.

TL;DR - Durability has its own tier ladder, priced out in the DR tiers reference: daily backups (included, up to ~24h RPO), PITR (~2-minute worst-case RPO, ~$100-$400/month), warm standby (34ms-1s). Reads are cheap to protect (edge cache, measured: byte-identical through a forced origin outage). Writes and auth need a warm standby, which works managed-to-managed with logical replication and sessions that survive cutover via third-party auth (TPA, Supabase’s mechanism for trusting an external issuer’s tokens) registration (measured: 34ms-1s replication lag, TPA resolution ~60-122ms) - with one hard boundary: platform-managed schemas (auth.*, storage.*) do not replicate at any tested size, so standby auth posture is token portability plus forced re-login, not user replication. The JWT clock-skew incident class has no runtime workaround - only exposure reduction (a token TTL lever that takes effect in ~6.5s) and detection (the PGRST code, not the HTTP status).

You need to survive…TierCostEvidence
Origin 5xx on read pathsEdge cache WorkerOne Worker, no infra changeMeasured, W04
JWT claim rejections (PGRST303)TTL raise + code-aware alertingConfig changeMeasured, W01/W03
Auth outage with active sessionsLonger TTL (sessions keep validating)Config changeMeasured, W03
The whole PostgREST/Kong API layer (PGRST303, routing rollouts)Own auth + claims over the wireRewrites the client; loses SDK surfaceMeasured, rls-wire-claims C01-C03 (reference)
Full regional/project lossWarm standby + cutoverSecond project + rehearsalMeasured, W05
Anything elseCold DR (pg_dump)Cron + object storageMeasured, W06
Data loss after the factPITR (point-in-time restore)~$100+/mo + Small computeDocumented (tiers)
Bad DELETE / bad migrationPITR - the standby replicates the mistake in 34ms~$100+/moDocumented
Org-wide 402 restrictionFix billing (card / cap / usage)-Documented
Fleet-wide incidentContract (SLA)Enterprise planDocumented

Thirteen classes is a lot; most readers need four. Start from your shape:

Your shapeReadsWrites / authDurabilityClasses that apply
Free, one projectedge cache (7)accept the outagescheduled db dump - Free has no backups1, 2, 4, 5, 7, 12
Pro, one project, productionedge cache (7)break-glass mint (W07) + TPA prepdaily backups (7d) or PITR1-8, 12
Team / Enterprise, productionedge cache + failover (7)warm standby (9)PITR + standby (tiers)all
Enterprise, federated IdPsamestandby; fresh logins work via the shared issuerPITR + standbyall; the auth.* caveat drops out

The rest of the page is the class catalog; the classes not in your row are lab-interesting until your shape changes. One caveat on the table’s plan axis: classes 3, 8, 10 and 11 are USAGE-gated, not plan-gated - they apply on any plan the moment you use the pooler, supabase-js, Edge Functions, or a query long enough to hit a timeout.

Mechanism. PostgREST validates iat/exp on every request with a 30-second clock-skew tolerance.1 If the issuer’s clock runs ahead of the validator’s, freshly minted tokens carry a future iat and are rejected - while tokens minted before the skew began keep working. Auth (/auth/v1/user) can return 200 while the Data API rejects the same token; the two validate on different hosts.2

Client sees. 401 {"code":"PGRST303","message":"JWT issued at future"}. The failure wave is every active session as its access token expires and refreshes - the default 1-hour TTL washes the whole user base through the broken path within one TTL.

Measured (edge-resilience W01, 2026-08-15). The documented tolerance is exact: +30s accepted, +31s rejected (401 PGRST303). Expired tokens reject with the same code; an unknown signing key rejects PGRST301. Two concurrent refreshes of the same token both return 200 (W08) - simultaneous reuse does not hard-fail.

Public incidents. 2026-08-14 “401 errors due to JWT rejections” on the Supabase status page (“newly refreshed JWTs being rejected by the API”), with an earlier “elevated JWT authorization errors” incident the same week and a matching community report three weeks prior.32 Three occurrences in a month.

Workarounds.

  • Reduce exposure: raise jwt_exp via the Management API. Readback is immediate for the new value; the issuer honours it ~6.5s later (measured, W03). The apply is async, so poll the readback after any config/auth PATCH including the restore - on 2026-08-17 an immediate readback returned the pre-restore value (W17). A 12h TTL means a 20-minute skew window touches a small fraction of sessions instead of all of them; the 12h figure is a choice, not a measurement.
  • Detect by code: alert on the PGRST303 rate. The HTTP status (401) is ambiguous; the code is not.
  • Do not retry mid-skew: every refreshed token is equally future-iat. The default client already does the right thing - exactly 1 attempt on a claim rejection (measured, W02, supabase-js 2.112.3).
  • No runtime fix exists. The repair is platform-side (PostgREST update plus NTP hardening). The only structural dodge is leaving the validating layer entirely: claims over the wire keeps per-user RLS while your own verifier is the only clock that matters (measured, rls-wire-claims C01/C02) - a rewrite, not a mitigation.

Measured (platform-downtime D01/D03/D04, 2026-08-04, n=1 per operation, one Micro project in ap-southeast-1, 500ms sampling). On restart: REST and Realtime zero failed samples, Auth 75 s (HTTP 521), Storage 78 s (HTTP 500), pooler 158 s (Failed to connect to database: {:error, :timeout}). The lab’s summary file (RELIABILITY.md) carried REST ~10s, Storage ~26s, Auth ~75s as N=3 p50 dated 2026-08-06..08; the RUNLOG is the record, and no artifact exists for those figures. A resize costs more than a restart, and not by one factor: Auth 131 s against 75 s (1.75x), pooler 207 s against 158 s (1.31x). REST and Realtime did not fail at all under four operations at 500ms sampling - the paths do not move together. The per-path matrix is in what a platform operation costs.

Two probe-design lessons generalise: anonymous network probes see nothing (network accept is not readiness), and the Auth probe must be the signUp REST surface - RLS blocks the password grant on a fresh project, so a password-grant sensor reports an outage that is really its own permission model. One more measured note for runbooks: pg_cron jobs resume across a project restart and keep firing on schedule - a 1/min heartbeat job had 2 rows before the restart and 5 after (3 new rows across the window), with no catch-up doubling (measured, edge-resilience W23, 2026-08-16).

Measured (pooler-semantics, http-tier-lockdown). Session mode: startup auth failure is a clean 58P01 at connect. Transaction mode: connect succeeds, the first statement fails 08P01/0A000, and prepared statements are unsupported. The same HTTP 400 can be a bad request or a capacity ceiling - read the SQLSTATE. Non-pooler 5432 can accept and then silently blackhole; always set connect and statement timeouts.

Class 4: Capacity and control-plane events

Section titled “Class 4: Capacity and control-plane events”

Documented (public status history, 2026). Recurring multi-region project creation/resize/restart failures: April APAC (~1.3h), April us-east (~2h), June multi-region (“existing projects are not affected unless restarted or resized”), August us-east-2 project access.3 Existing projects usually keep serving.

Workaround. Pre-provision a project pool if your product provisions per-tenant, and never couple app availability to control-plane availability. There is no runtime fallback.

Your own billing state is a class here too. Under the Fair Use Policy, continually exceeding the Free quota, exceeding the Pro quota with the spend cap on, or overdue invoices restrict the whole org: projects paused, databases read-only, launches blocked, and every API request answered 402 with a restriction description in the body.4 Detection is the 402 code in the probe table; the spend cap is not a request-path breaker (W21: quota+5 renders all 200); the fix is a card, a cap toggle, or reduced usage. The full billing/cost picture is in the DR tiers reference.

Measured (http-tier-lockdown). A PostgREST schema-cache wedge returns 503 PGRST002 “schema cache load”; the fix is pg_notify('pgrst', 'reload schema'). PGRST001 on an empty exposed schema is not a wedge. ACTIVE_HEALTHY means the HTTP tier is up, not that PostgREST can reach Postgres - probe a real table, never the status field.

Class 6: Storage render path failure modes

Section titled “Class 6: Storage render path failure modes”

Measured (edge-resilience W19, 2026-08-16). All six outcomes, verbatim:

Source objectRender URL (?width=32)Plain public URL
Valid PNG200, resized200
corrupt.png (text bytes)400 InvalidRequest - “The source image is invalid or unsupported for rendering”200, original bytes
SVG200, source SVG unchanged (no rasterisation)200

The render path never produced a 5xx, and the original always serves - which is exactly the degradation posture the workaround relies on.

Workarounds. Pre-generate renditions at upload (the docs-recommended architecture; the billing fix doubles as a resilience fix against the render path). The per-project transformations toggle is a hard stop with a caveat: cached transformed images may still bill after disabling, so do not promise a clean zero.

The billing half is a commercial decision, not an incident class: transformations bill per distinct origin image per billing cycle ($5 per 1,000, 100 included on Pro and Team, count resets each cycle), a growing library re-bills every month it is viewed, and the cap trade-off - cap on means eventual restriction, cap off means an uncapped bill - is priced out in the DR tiers reference.5

Class 7: Edge caching as an outage absorber

Section titled “Class 7: Edge caching as an outage absorber”

Measured (edge-resilience W04, 2026-08-15). A cache-first Cloudflare Worker served warm URLs 200 with byte-identical bodies while the origin was hard-down. Cold URLs failed - only warm reads survive.

A second platform behavior matters for failure simulation: Cloudflare Workers wraps TCP failures to unroutable addresses as a 403 response, not a JS exception - catch-based stale fallback never fires for that failure mode. Handle it in the status branch. The same wrap bites failover logic: a >=500-only failover condition never trips on an unroutable primary, because the failure arrives as a 403 response (measured, edge-resilience W24, 2026-08-16 - the shipped worker fails over on 5xx, 403, or any non-ok in outage mode).

The failover proxy, measured end to end (edge-resilience W24, 2026-08-16). The full sequence ran clean, every step HTTP 200: prime the probe URL (origin: primary) -> redeploy with the primary pointed at an unroutable address (origin: standby) -> restore the primary and probe immediately (origin: still standby - the HOLD_MS flap-damping window, persisted in the Cache API so it survives the redeploy) -> probe after the window (origin: primary). Getting that sequence to run green surfaced three traps, each measured as a bug before it became a rule:

  1. Cache-first masks failover. The first iteration measured origin “none” on the prime and outage probes: the cache-first read ran before the failover logic, and HIT responses carry no origin information. A failover mode behind a cache-first read is masked by warm entries - skip the cache when failing over, and tag every response with the origin that served it.
  2. The 403 wrap defeats >=500 failover conditions (the wrap described above). The shipped worker fails over on 5xx, 403, or any non-ok in outage mode.
  3. PostgREST 400s on unknown query params (treated as column filters). The drill’s ?_w24= cache-buster, forwarded to the origin, 400’d every probe - and a 400 is not a failover condition, so the outage phase served 400s tagged primary. Strip probe-only params from the origin URL; keep them only in the cache key.

Sizing note from the same drill: the hold window must exceed the redeploy-plus-settle path between the last outage probe (which refreshes the failure timestamp) and the holdover probe - ~11s observed, so HOLD_MS=15000 measured an expired window and 60000 is what the drill ships.

Routing table isolation (W25). The multi-tenant routing variant of the drill worker isolates failures per tenant: a stale routing row pointed at a dead origin fails only that tenant (502 for it, 200 elsewhere), and the eject costs a redeploy (10.6s) while the table lives in an env var (measured, edge-resilience W25).

Measured (edge-resilience W02, supabase-js 2.112.3). 401 PGRST303 -> exactly 1 attempt (no retry amplification). 503 x3 then 200 -> 4 attempts, success in ~7.0s. Connection refused -> surfaced in ~7.0s. The built-in retries cover 408/409/503/504 and network failures, on by default since v2.102.0; custom policies go through fetch-retry.6

Class 9: Warm standby and cutover (the HA tier)

Section titled “Class 9: Warm standby and cutover (the HA tier)”

The auth findings in this class and in classes 1 and 8 are consolidated, with the refresh, third-party-issuer and self-hosted GoTrue measurements that postdate them, in Supabase Auth end to end.

Measured (edge-resilience W05, 2026-08-15, cross-region ap-southeast-2 -> ap-southeast-1). The full HA path works on managed projects:

  • Logical replication, managed to managed: a subscription on the standby against the primary’s direct host (db.<ref>.supabase.co) works. Initial sync ~3.1-6.5s on a small table; replication lag 34ms-1057ms across regions. The pooler cannot be the source - it fails at the tenant-identifier layer (ENOIDENTIFIER).
  • Sessions survive cutover without copying secrets: register the primary’s OIDC issuer as a TPA integration on the standby (resolves in ~60-122ms) and primary-issued tokens read the standby’s API. Copying the JWT secret is not an option anyway - the config API accepts the write and changes nothing (measured 2026-08-14).
  • The cold path is real: a first-time issuer’s key costs ~30s of PGRST301 before PostgREST trusts it; a previously-seen JWKS warms in ~300ms. Rehearse the cutover before you need it.

Scope note: a standby replicates your mistakes too - a bad DELETE lands on the standby in 34ms. Logical corruption is PITR’s job, not the standby’s.

Cutover hygiene, all measured (2026-08-16 unless noted): sequences do not replicate - the first post-cutover insert fails with a verbatim duplicate-key error (duplicate key value violates unique constraint "w16_t_pkey"), and setval resync restores inserts (W16). DDL does not replicate, and primary-side DDL stalls ALL table replication - even rows not using the new column, because the apply worker holds streaming changes for any table not yet in r state - until the same DDL lands on the standby, which resumes replication in ~6.1s with backfill and no subscription recreation; apply standby DDL first (W15). Per-project auth config (SMTP, SITE_URL, redirect URLs, rate limits, jwt_exp) does not follow a cutover - re-apply it via the Management API; the diff is readable verbatim from GET /v1/projects/{ref}/config/auth on both projects (W17). Realtime has a matching trap from the earlier round: dropping and recreating a table under an active subscription kills events for that table NAME (new OID, stale channel metadata) - stable canary tables only (W12). And the wedged-subscription recovery is ordered: disable -> set (slot_name = none) -> drop subscription -> pg_drop_replication_slot on the publisher - dropping without the slot detach leaves the publisher slot pinning WAL.

The replication boundary (W09/W14, measured 2026-08-15/16). The early hypothesis was worker exhaustion on micro; sizing the standby up disproved it. pg_settings DO scale with size (max_connections 60 -> 90, shared_buffers 256MB -> 512MB) but max_worker_processes is 6 on both micro and small - platform-fixed. The failure mechanics differ by mode: with copy_data=true the initial sync stalls in pg_subscription_rel state d forever, the sync worker hung at IPC/BgworkerStartup with its publisher sync slot inactive; with copy_data=false the WAL sender connects but received_lsn stays NULL - zero changes stream either way, at any size. The discriminator: a custom non-public schema (lab_schema.t) replicates in ~4s on the same instances, while storage.buckets does not - and inserts via the postgres role change nothing, so the writer role is not the filter. The wall is specific to platform-managed schemas (auth.*, storage.*), not size, not workers, not schema privacy. The standby auth posture is therefore: TPA token portability for existing sessions, user backfill through the admin API or direct SQL, or forced re-login for fresh logins. POST /auth/v1/admin/users accepts the source’s bcrypt password_hash and the user keeps their password (measured, tenant-consolidation C03, 2026-08-04); copying hashes via direct SQL into auth.users is the other path (not measured on a standby). The W09 run (2026-08-15) posted its backfill users without a hash and recorded “not portable via the admin API” as an assumption; an earlier version of this paragraph repeated it, and the C03 measurement is what stands.

Sync at scale (W22). A 1,000,000-row initial sync completed in 22.7s (12.5s on a later battery run), with streaming lag ~245-276ms immediately after - the small-table numbers above extrapolate better than the stall stories suggested.

Cold DR floor (W06). pg_dump 12.4s, restore 6.4s for 10k rows through the pooler session host.

Break-glass (W07). GET /v1/projects/{ref}/postgrest returns the project’s jwt_secret, and a locally minted token authenticates against the live API with zero Auth involvement. During an Auth outage that is an escape hatch; the same read grants full token minting for the project. Prefer TPA portability - and rotate the secret after any break-glass use: the Management API read is auditable, but the secret stays live until rotated.

Measured (edge-resilience W13/W18, 2026-08-15/16). The wall clock is a 150s idle timeout - a sleeper function holding the connection past it dies with 504 IDLE_TIMEOUT (W13). Cold start costs ~1.4s on the first invoke after deploy plus idle; subsequent idle-cold invokes land at 121-302ms. Over 5 cold invokes at 60s gaps: p50 284ms, p99 1433ms; warm p50 over 20 invokes is 98ms (W18). Keep-warm pings buy the difference between 98ms and ~300ms, not the 1.4s - and long jobs belong on a queue plus cron, not in a request path. The deploy-time ceilings (functions per project, bundle size by deploy path, the four secrets limits) and the failures that only look like limits (silent loss under parallel deploys, the 413 that both size ceilings return) are measured separately in Edge Function limits, one ceiling at a time.

Class 11: Statement and lock timeout signatures

Section titled “Class 11: Statement and lock timeout signatures”

Measured (edge-resilience W20, 2026-08-16). Both surfaces return HTTP 400 - the SQLSTATE in the body is the signal, not the status:

  • statement_timeout (2s setting against pg_sleep(5)): verbatim ERROR 57014: canceling statement due to statement timeout, 3467ms wall.
  • lock_timeout (3s setting on an advisory-lock contender, two sessions): verbatim ERROR 55P03: canceling statement due to lock timeout, 4533ms wall.

Alert on the codes; a 400 alone is ambiguous with ordinary bad queries.

Documented (2026). Realtime limits are per plan: Free 200 concurrent connections / 100 messages per second; Pro 500 / 500; Pro without the spend cap and Team 10,000 / 2,500; Enterprise beyond that.7 Exceeding the message rate disconnects clients with a tenant_events error, and supabase-js reconnects automatically once throughput drops below the plan limit. The failure presents like an outage - sockets dropping en masse - but it is a quota: the signature is the disconnect reason, and the fix is plan or usage.

Fleet-wide platform incidents have no client-side answer - the remedy is contractual (SLA, degradation prioritisation). The platform uptime SLA is Enterprise-only - 99.9% per product per month, measured per-project, per-region or globally depending on the product, GA features only, with service credits of 10-30% of the affected service’s monthly fees (capped at 20% of the trailing twelve months’ fees) as the sole remedy.8 Support SLAs exist for Team and Enterprise: Urgent response is 24 hours 24/7x365 on Team and 1 hour 24/7x365 on Enterprise Standard and Priority Plus; business-hours limits start at High (Team: 1 business day for High and Normal, 2 business days for Low). The full severity table is on the SLA page.8 Everyone below Team is on best-effort

  • which is most of why this page exists. Read replicas are GET-only, never promoted, and Auth always goes to the Primary, so they absorb read load only.9 Multi-region active-active writes remain split-brain territory: dual-write returns 200/200 with ~107ms skew, but a partial failure (primary 200 / standby 400) is not atomic (W26) - fail over, never dual-write.

The practices the classes above support, one row each, with the number or error it addresses and the module it rests on. Module ids resolve in the edge-resilience RUNLOG, the platform-downtime RUNLOG, the instance-sizing RUNLOG and the compute-disk RUNLOG. The platform-downtime rows are n=1 per operation, so they rest on the ordering of the paths, not on the seconds.

PracticeRests on
Budget a maintenance window off the resize figure: Auth 131 s and the pooler 207 s resizing up, against 75 s and 158 s for a restart.platform-downtime D01 vs D03, 2026-08-04
Keep latency-sensitive reads on PostgREST across a planned restart or resize; REST and Realtime returned zero failed samples under all four operations while the pooler was down 158-207 s.platform-downtime full matrix, 2026-08-04
Read the pooler error string to identify the operation underway: {:error, :timeout} is a restart, {:error, :econnrefused} a resize up, terminating connection due to administrator command a resize down, (EADDRNOTALLOWED) address not in tenant allow_list a restriction just applied.platform-downtime pooler-mode table
When adding a network restriction, allow-list your pooler clients’ egress addresses as well as your direct ones: Supavisor enforces the list against the client address, so 6543 is covered and the refusal lands 1 s after sampling starts.platform-downtime D02, two runs
Retry the first write after ACTIVE_HEALTHY: 5 of 5 fresh projects refused the first POST /auth/v1/admin/users with 500 unexpected_failure and accepted the next attempt one poll tick later. Expect fresh-project Storage to answer TenantNotFound and then 429 SlowDown for the first minutes. A pre-provisioned project pool (Class 4) needs both.placement reference, Verified row ‘Create -> healthy, and healthy is not writable’ (2026-08-04, n=5, bash run not in the lab repo); supabase-lab AGENTS.md provisioning note (2026-08-03, 2 of 2, 500 "Database error checking email"); W21
Pass region_selection: {"type": "smartGroup", "code": "apac"} on POST /v1/projects when capacity matters more than the exact city: accepted on a Pro org with 201, landed ap-northeast-2, healthy in 135 s.instance-sizing I02, 2026-08-17
Retry Realtime joins with backoff: a join after 11 other modules had the socket closed before the event arrived. Never drop and recreate a table under an active subscription; events for that table name stop (new OID, stale channel metadata).W12
Keep the tenant routing table in KV or D1 rather than an env var: the env-var eject cost a redeploy at 10.6 s. The KV/D1 eject time itself is not measured.W25, 2026-08-17; the KV/D1 half is a design choice
Diff the whole GET /v1/projects/{ref}/config/auth payload between primary and standby rather than a named field list: custom_oauth_max_providers defaulted to 32767 on one project generation and 3 on another.W17 baseline diff, 2026-08-17
Poll the readback after any config/auth PATCH, including the restore in a drill; the apply is async and an immediate readback returned the pre-restore value.W17, 2026-08-17 battery (24/25, green on re-probe)
Check max_replication_slots before adding a subscription (10 on micro and on small) and drop orphan slots on the publisher: each subscription pins one, and a dropped subscription can leave its slot on the publisher either way - after a plain drop (W05) and after slot_name = none then drop (W14) - so list pg_replication_slots on the publisher and pg_drop_replication_slot any orphan first.compute-disk D01; W05, W14
Add the 520-class statuses to the client retry set through fetch-retry; the built-in set is 408/409/503/504 and network failures. Documented, not measured.supabase-js docs6; design row

Not measured, so not a row above: the KV/D1 eject time (W25 measured the env-var redeploy path only); whether a primary-side network restriction blocks a standby’s walreceiver (D02 restricted the pooler, the direct-host subscription path was never restricted); failing back by reversing the replication direction; the 12h jwt_exp value (the lever is measured, the value is a choice); read replicas keeping GETs alive through a lifecycle operation. One discrepancy is open: compute-disk D09 (2026-08-19, local vantage, 250 ms) saw 0 s of contiguous outage on /auth/v1/health across four resizes, while platform-downtime D03 (2026-08-04, 500 ms) saw 131 s of HTTP 521 on the same /auth/v1/health for one. Different rigs, projects and vantages, so it is a side-by-side run to do; no module has probed an authenticated Auth operation across a resize.

What is failing?Reads 5xx / origin down401 PGRST303 waveLogins / refresh deadProject or region lostCache-first Worker (W04)Raise jwt_exp (W03)+ alert on codeLonger TTL:existing tokens still validateWarm standby + TPA cutover (W05)rehearsed
ClaimValueHow it was checked
Skew tolerance boundary+30s pass, +31s PGRST303Measured (W01, two runs)
jwt_exp acceptance-to-effect~6.5sMeasured (W03)
supabase-js on PGRST3031 attemptMeasured (W02, mock)
Edge cache under origin outage200, byte-identicalMeasured (W04, forced outage)
Standby replication lag34ms-1057msMeasured (W05, three runs)
Standby initial sync, 1M rows22.7s / 12.5s; lag after ~245-276msMeasured (W22, two runs)
auth./storage. replicationzero changes at any tested sizeMeasured (W09/W14)
DDL on primary stalls all table replicationresume ~6.1s after standby DDLMeasured (W15)
Post-cutover insert without resyncduplicate key; setval fixesMeasured (W16)
Edge function cold start~1.4s first-after-idle only; p50 284ms cold vs 98ms warmMeasured (W18)
Edge function wall clock150s idle -> 504 IDLE_TIMEOUTMeasured (W13)
Render path on invalid source400 InvalidRequest; original still servesMeasured (W19)
statement_timeout signature57014 verbatim, 3467ms wallMeasured (W20)
lock_timeout signature55P03 verbatim, 4533ms wallMeasured (W20)
pg_cron across restartresumes on schedule (2 -> 5 rows), no doublingMeasured (W23)
Edge failover sequenceprimary -> standby -> holdover -> primary, all 200Measured (W24)
Tenant routing isolationpoisoned row: 502 for its tenant, 200 elsewhere; eject = redeploy (10.6s) via env-var tableMeasured (W25)
Storage dual-writeparallel 200/200, ~107ms skew, bytes equal; partial failure not atomic; sync-after 97msMeasured (W26)
Spend cap enforcementnone at the request path (quota+5 renders all 200)Measured (W21)
TPA resolution on standby~60-122msMeasured (W05)
Cold kid trust on cutover~30s PGRST301Measured (W05 full suite)
Cold DR, 10k rowsdump 12.4s, restore 6.4sMeasured (W06)
Break-glass minting works200 real / 401 wrong secretMeasured (W07)
Concurrent refresh raceboth 200Measured (W08)
Restart gaps REST/Realtime/Auth/Storage/pooler0 failed samples / 0 / 75 s / 78 s / 158 sMeasured (platform-downtime D01, 2026-08-04, n=1, 500 ms); RELIABILITY.md carried ~10s / ~26s / ~75s (N=3) with no artifact
Resize up Auth/Storage/pooler131 s / 127 s / 207 sMeasured (platform-downtime D03, 2026-08-04, n=1)
Fresh project: first write after ACTIVE_HEALTHY5 of 5 refused with 500, next attempt acceptedMeasured elsewhere (placement reference, Verified row ‘Create -> healthy, and healthy is not writable’ (2026-08-04, n=5, bash run not in the lab repo); supabase-lab AGENTS.md provisioning note (2026-08-03, 2 of 2, 500 "Database error checking email"); summarised in operation cost)
Smart-group region selection on a Pro org201, landed ap-northeast-2, healthy in 135 sMeasured (instance-sizing I02, 2026-08-17)
max_replication_slots on micro and small10 / 10Measured (compute-disk D01)
30s documented tolerancedocumentedPostgREST v11 docs1
Transform billing model$5/1000 distinct origins/cycleSupabase docs5

All measured rows come from the supabase-lab pvlab harness (experiments/edge-resilience), 2026-08-15/16/17, and are reproducible from the experiment’s Makefile - the full run log with every number is RUNLOG.md. Batteries: 2026-08-16, 22 modules, 22/22 (out/2026-08-16); 2026-08-17, 25 modules, 24/25 with W17 green on re-probe after its readback race was fixed (out/2026-08-17). The platform-downtime, instance-sizing and compute-disk rows cite their RUNLOGs at the same commit; those experiments have no published out/ artifact.

  1. PostgREST, “API Configuration and Custom Claims,” PostgREST Docs v11. https://docs.postgrest.org/en/v11/references/auth.html 2

  2. Supabase GitHub Discussion #48123, “Fresh Supabase Auth JWT rejected by PostgREST: PGRST303 ‘JWT issued at future’,” 2026-07-21. https://github.com/orgs/supabase/discussions/48123 2

  3. Supabase, “Status - Incident History,” status.supabase.com. https://status.supabase.com/ 2

  4. Supabase, “Billing FAQ - Fair Use Policy,” Supabase Docs. https://supabase.com/docs/guides/platform/billing-faq#fair-use-policy

  5. Supabase, “Manage Storage Image Transformations usage,” Supabase Docs. https://supabase.com/docs/guides/platform/manage-your-usage/storage-image-transformations 2

  6. Supabase, “Automatic retries in supabase-js,” Supabase Docs. https://supabase.com/docs/guides/api/automatic-retries-in-supabase-js 2

  7. Supabase, “Realtime Quotas,” Supabase Docs. https://supabase.com/docs/guides/realtime/limits

  8. Supabase, “Service Level Agreement.” https://supabase.com/sla 2

  9. Supabase, “Read Replicas,” Supabase Docs. https://supabase.com/docs/guides/platform/read-replicas