Skip to content

Supabase incidents: what a client can 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 workarounds are implemented in the resilience runbook; the RPO/RTO/monthly-cost axis for the durability tiers is in the DR tiers reference.

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). 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

Most readers need four of the thirteen classes, depending on their 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

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.

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

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

Measured in 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.

The Supabase status page logged “401 errors due to JWT rejections” on 2026-08-14 (“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.

  • Raise jwt_exp via the Management API to reduce exposure. 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.
  • Alert on the PGRST303 rate. The HTTP status (401) is ambiguous; the code identifies the class.
  • Do not retry mid-skew; every refreshed token is equally future-iat. The default client makes 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 alternative is leaving the validating layer entirely: claims over the wire keeps per-user RLS with your own verifier as the only clock (measured, rls-wire-claims C01/C02) - a rewrite, not a mitigation.

On restart (platform-downtime D01/D03/D04, 2026-08-04, n=1 per operation, one Micro project in ap-southeast-1, 500ms sampling), REST and Realtime had 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, by a different factor per path: 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 per-path matrix is in what a platform operation costs.

For probe design, 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. 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 in pooler-semantics and http-tier-lockdown, a session-mode startup auth failure is a clean 58P01 at connect. In 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”

The 2026 public status history documents 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.

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 belongs in this class. 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 in 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 in 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 the workaround relies on.

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, though 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 in 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.

Cloudflare Workers wraps TCP failures to unroutable addresses as a 403 response rather than a JS exception, so catch-based stale fallback never fires for that failure mode; handle it in the status branch. The same wrap affects failover logic: a >=500-only failover condition never trips on an unroutable primary, because the failure arrives as a 403 response.

Measured in edge-resilience W24 (2026-08-16), the failover proxy ran end to end, 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). Three bugs, each measured, had to be fixed before the sequence ran green:

  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.

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.

The multi-tenant routing variant of the drill worker (W25) 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 in edge-resilience W02 against 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.

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

  • Logical replication works managed to managed, with a subscription on the standby against the primary’s direct host (db.<ref>.supabase.co). 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 once the primary’s OIDC issuer is registered as a TPA integration on the standby (resolves in ~60-122ms); primary-issued tokens then 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).
  • 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.

A standby replicates your mistakes too: a bad DELETE lands on the standby in 34ms, so recovering from logical corruption needs PITR.

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). In Realtime, 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.

W09 and W14 (2026-08-15/16) located the replication boundary. Sizing the standby up ruled out worker exhaustion on micro: 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 differs 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. 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 boundary is specific to platform-managed schemas (auth.*, storage.*); size, worker count and schema privacy were each ruled out. The standby auth options are 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, so its “not portable via the admin API” note was an assumption; C03 measured the hash path.

A 1,000,000-row initial sync (W22) completed in 22.7s (12.5s on a later battery run), with streaming lag ~245-276ms immediately after, so the small-table lag figures still hold at that size, and the sync scales without the stall the earlier runs suggested.

The cold DR floor (W06) is pg_dump 12.4s and restore 6.4s for 10k rows through the pooler session host.

For break-glass access (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 keeps the API usable; 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 in 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 recover the difference between 98ms and ~300ms and do not remove the 1.4s; long jobs belong on a queue plus cron rather than 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 in edge-resilience W20 (2026-08-16), both surfaces return HTTP 400, and the SQLSTATE in the body is what tells them apart:

  • 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.

Realtime limits are per plan (documented, 2026): 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. It looks like an outage, with sockets dropping en masse; the disconnect reason identifies it as a quota, 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. 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 risk split-brain: dual-write returns 200/200 with ~107ms skew, but a partial failure (primary 200 / standby 400) is not atomic (W26), so fail over rather than dual-write.

One row per practice, 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.

PracticeEvidenceModule
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
Allow-list your pooler clients’ egress addresses as well as your direct ones.A network restriction is enforced by Supavisor 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, and the first Storage calls on a fresh project.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. Fresh-project Storage answers 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 a smart region group on POST /v1/projects when capacity matters more than the exact city.region_selection: {"type": "smartGroup", "code": "apac"} was 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, and never drop and recreate a table under an active subscription.A join after 11 other modules had the socket closed before the event arrived. After a drop and recreate, 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 config/auth payload between primary and standby rather than a named field list.On GET /v1/projects/{ref}/config/auth, 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 and drop orphan slots on the publisher first.The limit is 10 on micro and on small. 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.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: 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.

ModuleExperimentTestArtifact
C01rls-wire-claimsc01-wire-claims.tsnone published
C03tenant-consolidationc03-users-merge-api.tsnone published
D01compute-diskd01-compute-pg-limits.tsnone published
D01platform-downtimed01-restart.tsnone published
D02platform-downtimed02-restriction-flip.tsnone published
D03platform-downtimed03-resize-up.tsnone published
D04platform-downtimed04-resize-down.tsnone published
D09compute-diskd09-compute-resize-timing.tsnone published
I02instance-sizingi02-smart-region.tsnone published
W01edge-resiliencew01-jwt-skew.tsout/2026-08-16, out/2026-08-17
W02edge-resiliencew02-retry-probe.tsout/2026-08-16, out/2026-08-17
W03edge-resiliencew03-jwt-exp.tsout/2026-08-16, out/2026-08-17
W04edge-resiliencew04-edge-cache-stale.tsout/2026-08-16, out/2026-08-17
W05edge-resiliencew05-standby-replication.tsout/2026-08-16, out/2026-08-17
W06edge-resiliencew06-cold-dr-timing.tsout/2026-08-16, out/2026-08-17
W07edge-resiliencew07-breakglass-mint.tsout/2026-08-16, out/2026-08-17
W08edge-resiliencew08-refresh-race.tsout/2026-08-16, out/2026-08-17
W09edge-resiliencew09-auth-replication.tsout/2026-08-16, out/2026-08-17
W12edge-resiliencew12-realtime-probe.tsout/2026-08-16, out/2026-08-17
W13edge-resiliencew13-function-timeout.tsout/2026-08-16, out/2026-08-17
W14edge-resilienceRUNLOG-
W15edge-resiliencew15-ddl-during-replication.tsout/2026-08-16, out/2026-08-17
W16edge-resiliencew16-sequence-resync.tsout/2026-08-16, out/2026-08-17
W17edge-resiliencew17-config-parity.tsout/2026-08-16, out/2026-08-17
W18edge-resiliencew18-function-coldstart.tsout/2026-08-16, out/2026-08-17
W19edge-resiliencew19-render-failures.tsout/2026-08-16, out/2026-08-17
W20edge-resiliencew20-statement-timeout.tsout/2026-08-16, out/2026-08-17
W21edge-resiliencew21-spend-cap.tsout/2026-08-17
W22edge-resiliencew22-bulk-sync.tsout/2026-08-16, out/2026-08-17
W23edge-resiliencew23-cron-restart.tsout/2026-08-16, out/2026-08-17
W24edge-resiliencew24-edge-failover-proxy.tsout/2026-08-16, out/2026-08-17
W25edge-resiliencew25-routing-eject.tsout/2026-08-17
W26edge-resiliencew26-storage-dual-write.tsout/2026-08-17
  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 ↩