Bug report
One subscriber whose stored claims make RLS evaluation raise causes postgres_changes delivery to stop for every subscriber on the project — silently. We hit this in production on 2026-08-13 and lost all realtime delivery for ~2.5 hours, platform-wide.
Environment
- Hosted Supabase, eu-north-1 (project ref available via support ticket on request)
- Realtime 2.125.1 (slot
supabase_realtime_replication_slot_2_125_1_*)
- Postgres 15, RLS enabled on all published tables
What happened
- For ~2 hours (coinciding with a platform-side restart of our project's Auth + Realtime at ~05:04 UTC), GoTrue-issued access tokens carried the string
"null" instead of JSON null in nullable app_metadata claims (e.g. app_id). Our access-token hook stamps proper JSON nulls and was byte-identical on our dev project, which was unaffected — the mangling was platform-side and stopped on its own at ~07:19 UTC. That is a separate Auth issue; it matters here only as the trigger.
- Our RLS policies call a helper that does
NULLIF(auth.jwt() -> 'app_metadata' ->> 'app_id', '')::uuid. With the claim being the string "null", this raises 22P02 invalid input syntax for type uuid: "null".
realtime.apply_rls() evaluates policies per subscription using the claims stored at subscribe time. When evaluation for one subscription raises, the error aborts processing of the WAL batch — and no subscriber receives anything, including service_role subscriptions and subscriptions on unrelated tables.
Observed symptoms
- WebSocket connects (101), channels reach
SUBSCRIBED, rows appear in realtime.subscription with correct claims/filters — everything looks healthy.
- The replication slot stays
active with confirmed_flush_lsn advancing to pg_current_wal_lsn() — WAL is consumed and acknowledged.
- Zero
postgres_changes frames are delivered to any subscriber (verified with a service_role client subscribed with and without filters, on multiple tables).
- Reproduced directly: calling
realtime.apply_rls() with a wal2json-shaped record while the poisoned subscriptions were present raises 22P02 with context SQL statement "execute walrus_rls_stmt"; after hardening our helper to not raise, the same call returns records and delivery resumed instantly for all subscribers.
Expected behavior
An error while evaluating RLS for one subscription should disqualify that subscription (arguably with an error surfaced to that channel), not abort delivery for every subscriber on the project. JWT claims are external input from the subscriber's perspective of the pipeline — any upstream serializer defect (as happened here, in the platform's own Auth layer) becomes a total, silent, project-wide realtime outage that no health signal exposes: sockets up, subscriptions registered, slot advancing, nothing delivered.
Workaround for anyone else hitting this
Make every function reachable from an RLS policy raise-proof against malformed claims — validate instead of casting:
CREATE OR REPLACE FUNCTION public.claim_uuid(p_claim text)
RETURNS uuid LANGUAGE sql IMMUTABLE SET search_path = '' AS $$
SELECT CASE
WHEN p_claim ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
THEN p_claim::uuid
END;
$$;
Diagnosis query that found our poisoned subscriptions, in case it helps others:
SELECT jsonb_typeof(claims->'app_metadata'->'app_id'), count(*)
FROM realtime.subscription GROUP BY 1;
-- 'string' where you expect 'null' = poisoned tokens
Happy to provide timestamps, the project ref, and full reproduction detail through a support ticket — please reference this issue.
Bug report
One subscriber whose stored claims make RLS evaluation raise causes
postgres_changesdelivery to stop for every subscriber on the project — silently. We hit this in production on 2026-08-13 and lost all realtime delivery for ~2.5 hours, platform-wide.Environment
supabase_realtime_replication_slot_2_125_1_*)What happened
"null"instead of JSON null in nullableapp_metadataclaims (e.g.app_id). Our access-token hook stamps proper JSON nulls and was byte-identical on our dev project, which was unaffected — the mangling was platform-side and stopped on its own at ~07:19 UTC. That is a separate Auth issue; it matters here only as the trigger.NULLIF(auth.jwt() -> 'app_metadata' ->> 'app_id', '')::uuid. With the claim being the string"null", this raises22P02 invalid input syntax for type uuid: "null".realtime.apply_rls()evaluates policies per subscription using the claims stored at subscribe time. When evaluation for one subscription raises, the error aborts processing of the WAL batch — and no subscriber receives anything, includingservice_rolesubscriptions and subscriptions on unrelated tables.Observed symptoms
SUBSCRIBED, rows appear inrealtime.subscriptionwith correct claims/filters — everything looks healthy.activewithconfirmed_flush_lsnadvancing topg_current_wal_lsn()— WAL is consumed and acknowledged.postgres_changesframes are delivered to any subscriber (verified with aservice_roleclient subscribed with and without filters, on multiple tables).realtime.apply_rls()with a wal2json-shaped record while the poisoned subscriptions were present raises22P02with contextSQL statement "execute walrus_rls_stmt"; after hardening our helper to not raise, the same call returns records and delivery resumed instantly for all subscribers.Expected behavior
An error while evaluating RLS for one subscription should disqualify that subscription (arguably with an error surfaced to that channel), not abort delivery for every subscriber on the project. JWT claims are external input from the subscriber's perspective of the pipeline — any upstream serializer defect (as happened here, in the platform's own Auth layer) becomes a total, silent, project-wide realtime outage that no health signal exposes: sockets up, subscriptions registered, slot advancing, nothing delivered.
Workaround for anyone else hitting this
Make every function reachable from an RLS policy raise-proof against malformed claims — validate instead of casting:
Diagnosis query that found our poisoned subscriptions, in case it helps others:
Happy to provide timestamps, the project ref, and full reproduction detail through a support ticket — please reference this issue.