Same bug on a different client — it's gateway-side, not platform-specific
Confirming this on a self-hosted instance (Docker Compose, gateway ghcr.io/fluxerapp/fluxer-gateway:v1, Postgres backend) with a GrapheneOS + Brave (Chromium) PWA — i.e. not iOS-specific. Same symptom exactly: web push works while the app is foregrounded/online, and stops entirely once presence transitions to offline, even though the subscription stays valid and registered.
I isolated where it breaks. Short version: the client/delivery side is fully healthy; the gateway never sends the push for an offline recipient.
Ruled out (all verified working)
A raw VAPID Web Push sent directly to the stored subscription (via the web-push CLI, bypassing the gateway entirely) is delivered to the device instantly, even with the browser fully closed. So the VAPID keypair, the stored subscription, FCM transport, and OS-level delivery are all fine.
The gateway's only push-related config is VAPID (which is all standard Web Push needs, and is the same path the manual send used successfully).
Gateway-internal evidence
Gathered via the release's bounded rpc against the live node (bin/fluxer_gateway.real rpc <Mod> <Fun> <Args>, with FLUXER_ERLANG_NODE_NAME / FLUXER_ERLANG_COOKIE / FLUXER_ERLANG_DIST_PORT set):
push:get_cache_stats() → #{push_subscriptions_size => 0, user_guild_settings_size => 4, badge_counts_size => 0, blocked_ids_size => 0} — the guild-settings sync path populates, but push subscriptions never do.
push_token_cache:get(UserId) → undefined.
push_dispatcher:stats() → #{queued => 0, inflight => 0} — even immediately after sending both a DM and a guild @mention to the offline recipient. Nothing is ever enqueued.
Where it breaks
A message to an offline recipient never reaches push_subscriptions:fetch_and_send_subscriptions/8 — the dispatcher stays empty. So the break is upstream of any subscription fetch/send, in the push:handle_message_create/1 → push_eligibility:is_eligible_for_push/8 path.
Note the module surface: there are sync_user_guild_settings / sync_user_blocked_ids paths (which work — hence user_guild_settings_size => 4), but there is no load/sync for push subscriptions, only invalidate_user_subscriptions. That's consistent with subscriptions being fetched on-demand at send time — which means the eligibility/dispatch step simply never fires for offline recipients, so the fetch never happens.
Happy to run more targeted rpc probes on the live node if a maintainer can point at which eligibility branch or dispatch call to instrument.
(Investigation and diagnosis performed with Claude Code.)
Thread
Comment by @chowe99
ghcr.io/fluxerapp/fluxer-gateway:v1, Postgres backend) with a GrapheneOS + Brave (Chromium) PWA — i.e. not iOS-specific. Same symptom exactly: web push works while the app is foregrounded/online, and stops entirely once presence transitions to offline, even though the subscription stays valid and registered. I isolated where it breaks. Short version: the client/delivery side is fully healthy; the gateway never sends the push for an offline recipient.Ruled out (all verified working)
web-pushCLI, bypassing the gateway entirely) is delivered to the device instantly, even with the browser fully closed. So the VAPID keypair, the stored subscription, FCM transport, and OS-level delivery are all fine.Gateway-internal evidence
Gathered via the release's bounded rpc against the live node (bin/fluxer_gateway.real rpc <Mod> <Fun> <Args>, withFLUXER_ERLANG_NODE_NAME/FLUXER_ERLANG_COOKIE/FLUXER_ERLANG_DIST_PORTset):presence_manager:lookup(UserId)→{error, not_found}.push:get_cache_stats()→#{push_subscriptions_size => 0, user_guild_settings_size => 4, badge_counts_size => 0, blocked_ids_size => 0}— the guild-settings sync path populates, but push subscriptions never do.push_token_cache:get(UserId)→undefined.push_dispatcher:stats()→#{queued => 0, inflight => 0}— even immediately after sending both a DM and a guild@mentionto the offline recipient. Nothing is ever enqueued.Where it breaks
A message to an offline recipient never reachespush_subscriptions:fetch_and_send_subscriptions/8— the dispatcher stays empty. So the break is upstream of any subscription fetch/send, in thepush:handle_message_create/1→push_eligibility:is_eligible_for_push/8path. Note the module surface: there aresync_user_guild_settings/sync_user_blocked_idspaths (which work — henceuser_guild_settings_size => 4), but there is no load/sync for push subscriptions, onlyinvalidate_user_subscriptions. That's consistent with subscriptions being fetched on-demand at send time — which means the eligibility/dispatch step simply never fires for offline recipients, so the fetch never happens. Happy to run more targeted rpc probes on the live node if a maintainer can point at which eligibility branch or dispatch call to instrument. (Investigation and diagnosis performed with Claude Code.)