Skip to content

Realtime auto-reconnect doesn't work correctly: it reconnects the websocket but the channel gets CLOSED #1311

@SmaugPool

Description

@SmaugPool

Describe the bug

On websocket connection closed, auto-reconnect connects the websocket again and tries to rejoin channels but this doesn't work, the channel stays closed:

2025-11-16 16:41:21,029 - INFO - channel=ChannelStates.JOINED
2025-11-16 16:41:21,030 - INFO - send: {"event":"heartbeat","payload":{},"topic":"phoenix","ref":null,"join_ref":null}    
2025-11-16 16:41:21,067 - INFO - receive: '{"ref":null,"event":"phx_reply","payload":{"status":"ok","response":{}},"topic":"phoenix"}'    
2025-11-16 16:41:21,068 - INFO - parsed message as event=<ChannelEvents.reply: 'phx_reply'> topic='phoenix' payload=SuccessReplyMessage(status='ok', response=ReplyPostgresChanges(postgres_changes=None)) ref=None    

2025-11-16 16:41:27,470 - ERROR - WebSocket connection closed with code: 1006, reason:     
2025-11-16 16:41:27,470 - INFO - Initiating auto-reconnect sequence...    
2025-11-16 16:41:27,470 - INFO - Attempting to connect to WebSocket at wss://xxx.supabase.co/realtime/v1/websocket?apikey=sb_secret_xxx                                                 
2025-11-16 16:41:27,685 - INFO - WebSocket connection established successfully    

2025-11-16 16:41:27,685 - INFO - Rejoining channel after reconnection: realtime:gateways    
2025-11-16 16:41:27,690 - INFO - send: {"event":"phx_leave","payload":{},"topic":"realtime:gateways","ref":"2","join_ref":null}    
2025-11-16 16:41:27,692 - INFO - send: {"event":"phx_join","payload":{"config":{"broadcast":null,"presence":{"key":"","enabled":false},"private":true,"postgres_changes":[]},"access_token":"sb_secret_xxx"},"topic":"realtime:gateways","ref":"3","join_ref":null}                                

2025-11-16 16:41:27,692 - INFO - send: {"event":"heartbeat","payload":{},"topic":"phoenix","ref":null,"join_ref":null}    
2025-11-16 16:41:27,712 - INFO - receive: '{"ref":"2","event":"phx_reply","payload":{"status":"ok","response":{}},"topic":"realtime:gateways"}'    
2025-11-16 16:41:27,713 - INFO - parsed message as event=<ChannelEvents.reply: 'phx_reply'> topic='realtime:gateways' payload=SuccessReplyMessage(status='ok', response=ReplyPostgresChanges(postgres_changes=None)) ref='2'    
2025-11-16 16:41:27,713 - INFO - realtime:gateways : event=<ChannelEvents.reply: 'phx_reply'> topic='realtime:gateways' payload=SuccessReplyMessage(status='ok', response=ReplyPostgresChanges(postgres_changes=None)) ref='2'    

2025-11-16 16:41:27,713 - INFO - channel realtime:gateways leave    
2025-11-16 16:41:27,713 - INFO - channel realtime:gateways closed    

2025-11-16 16:41:27,821 - INFO - receive: '{"ref":"3","event":"phx_reply","payload":{"status":"ok","response":{"postgres_changes":[]}},"topic":"realtime:gateways"}'    
2025-11-16 16:41:27,827 - INFO - parsed message as event=<ChannelEvents.reply: 'phx_reply'> topic='realtime:gateways' payload=SuccessReplyMessage(status='ok', response=ReplyPostgresChanges(postgres_changes=[])) ref='3'    
2025-11-16 16:41:27,827 - INFO - receive: '{"ref":null,"event":"phx_reply","payload":{"status":"ok","response":{}},"topic":"phoenix"}'    
2025-11-16 16:41:27,827 - INFO - parsed message as event=<ChannelEvents.reply: 'phx_reply'> topic='phoenix' payload=SuccessReplyMessage(status='ok', response=ReplyPostgresChanges(postgres_changes=None)) ref=None    
2025-11-16 16:41:37,702 - WARNING - subscription status=RealtimeSubscribeStates.TIMED_OUT err=None    
2025-11-16 16:41:46,039 - INFO - channel=ChannelStates.CLOSED

2025-11-16 16:41:52,698 - INFO - send: {"event":"heartbeat","payload":{},"topic":"phoenix","ref":null,"join_ref":null}    
2025-11-16 16:41:52,737 - INFO - receive: '{"ref":null,"event":"phx_reply","payload":{"status":"ok","response":{}},"topic":"phoenix"}'    
2025-11-16 16:41:52,737 - INFO - parsed message as event=<ChannelEvents.reply: 'phx_reply'> topic='phoenix' payload=SuccessReplyMessage(status='ok', response=ReplyPostgresChanges(postgres_changes=None)) ref=None    
2025-11-16 16:42:11,044 - INFO - channel=ChannelStates.CLOSED

I suspect this might be linked to the fact that the phx_leave and phx_join messages are sent consecutively without waiting for the first one reply.

Reproduction

import asyncio
import logging
from supabase.client import AsyncClient

logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")

async def main(args):
    client = AsyncClient(url, key)
    await client.realtime.connect()
    channel = client.channel("test", {"config": {"private": True}})

    def on_subscribe(status, err):
        logging.warning(f"subscription status={status} err={err}")

    def on_message(msg):
        logging.info(f"{msg['event']}")

    await (channel.on_broadcast("INSERT", on_message).subscribe(on_subscribe))

    while True:
        logging.info(f"channel={channel.state}")
        await asyncio.sleep(25)

asyncio.run(main(args))

Steps to reproduce

Run the snippet above and wait for a disconnection (or provoke it)

Library affected

realtime

Library version

supabase 2.24.0

Python version

python 3.12.3

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions