Reproduction steps
- Join a voice channel from the desktop client.
- Kill the client, or cut its network, for about a minute.
- Reopen it (or restore the network) and let it rejoin voice on its own.
Expected behaviour
The user shows in the voice channel again after the automatic rejoin.
Actual behaviour
The user can talk and hear, but does not appear in the voice channel for anyone. It stays that way until they leave the channel by hand (workaround: leave and rejoin manually).
Build information
Stable Desktop 2026.1026.24218, Web 2026.1031.143121, Linux 7.3.0-4-cachyos (x64), Electron 44.9.1, Chrome 152.0.7977.167, Node 24.22.0, Locale en-US
Server: self-hosted Docker Compose bundle 2026.1028.163925, LiveKit 1.12.0. LiveKit's client info at the rejoin: JS SDK 2.22.3, Electron 44.9.1, Linux. (Build string is as of 2026-10-31; the logged incident was 2026-10-29, so the web build may have updated since.)
Platform
Linux, Self-hosting
Evidence
Timeline from the gateway and LiveKit logs, 2026-10-29 UTC, IDs removed:
A second user shows the same pattern on 2026-10-31: LiveKit resume, leave and fresh join with no gateway voice state update, after a run of gateway session drops.
Likely cause
| Time | Source | Event |
|---|---|---|
| 01:01:16 | gateway | voice_state_update_received → voice_server_update_dispatch connection_id=<conn> (normal join) |
| 01:01:16 | LiveKit | starting RTC session / participant active for <conn> |
| 05:51:15 | LiveKit | participant closing, reason PEER_CONNECTION_DISCONNECTED (dtls timeout) |
| 05:52:35 | gateway | old session: voice_disconnect_start … connection_id=null → voice_disconnect_ok (voice state cleared) |
| 05:52:45 | LiveKit | could not handle new participant: could not restart participant (client tries to resume the old session) |
| 05:52:50 | LiveKit | starting RTC session for the same <conn>, Reconnect: false, then participant active |
| 05:52:35 – 08:36:14 | gateway | no voice_state_update_received from this user |
| 08:36:14 | gateway | new gateway session sends a voice state update for <conn> (the user leaving) |
- Client: the voice auto-reconnect path reuses the cached LiveKit token and connection name and goes straight to LiveKit, without re-sending a Voice State Update after its gateway session was replaced. The rejoin should go through the gateway, or re-assert the voice state once the new gateway session is ready.
- Server (hardening): LiveKit still accepts the old token after the gateway has cleared that voice state, and nothing removes a LiveKit participant that has no matching voice state. Possible fix: revoke/expire the connection when its voice state is cleared, or remove participants without a voice state on the LiveKit
participant_joinedwebhook.