"Channel Full" counts devices, not people: one user on two devices (or a crashed tab) locks everyone else out, though the server would let them in #342

Closed
opened 2026-10-10 22:15:43 -04:00 by jared · 1 comment
Owner

useVoiceChannelFull (src/app/hooks/useVoiceChannelFull.ts) decides channelFull = callMembers.length >= maxUsers. callMembers holds the MatrixRTC memberships, one per device. The server-side guard (matrix livekit/voice-limit-guard.py, should_block) counts distinct users: matrix_user() drops the device, and an extra device of someone already present is never blocked. The hook's doc comment says the join paths should "agree on when a channel is full", but the client and server disagree, and the client is the stricter one.

Reproduced (local stack; Voice Lounge io.lotus.voice_limit {max_users: 2}): @aud_main joined from two sessions, so there were two memberships (…_JJUFEUJJYV, …_RBZEQFZKLZ) but one person. Then @aud_bob opened the room:

  • Bob's prescreen and the header show "Channel Full (2/2) — Wait for someone to leave before joining." and Join is disabled;
  • the guard's /sfu/get for Bob at the same moment returned HTTP 200, so it would have admitted him.

When it happens in practice:

  • Someone is in a call on desktop and phone, e.g. phone for audio and PC for screen share.
  • A tab or app crashed or was killed and its membership hasn't expired yet. During this audit a stale membership from a killed browser made a call with one person show 2/2.
  • With small limits (2–5), one such user makes the channel look full.

The header counter (RoomViewHeader, "2/2") is wrong the same way.

Fix: count distinct users, matching the guard:

  • current = new Set(callMembers.map((m) => m.sender)).size;
  • channelFull = maxUsers > 0 && !alreadyMember && current >= maxUsers.

Add a unit test with two memberships from one user. Optionally show "2 people (3 devices)" in the tooltip.

Bob sees "Channel Full (2/2)" with one person (two devices) in the call

voice-devices-bob.png


Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny 30e2dbcd, cinny-desktop 8f67ec7, element-call 3b0050e8.

`useVoiceChannelFull` (src/app/hooks/useVoiceChannelFull.ts) decides `channelFull = callMembers.length >= maxUsers`. `callMembers` holds the MatrixRTC memberships, **one per device**. The server-side guard (matrix `livekit/voice-limit-guard.py`, `should_block`) counts **distinct users**: `matrix_user()` drops the device, and an extra device of someone already present is never blocked. The hook's doc comment says the join paths should "agree on when a channel is full", but the client and server disagree, and the client is the stricter one. **Reproduced** (local stack; Voice Lounge `io.lotus.voice_limit {max_users: 2}`): `@aud_main` joined from two sessions, so there were two memberships (`…_JJUFEUJJYV`, `…_RBZEQFZKLZ`) but one person. Then `@aud_bob` opened the room: - Bob's prescreen and the header show **"Channel Full (2/2) — Wait for someone to leave before joining."** and **Join is disabled**; - the guard's `/sfu/get` for Bob at the same moment returned **HTTP 200**, so it would have admitted him. **When it happens in practice:** - Someone is in a call on desktop and phone, e.g. phone for audio and PC for screen share. - A tab or app crashed or was killed and its membership hasn't expired yet. During this audit a stale membership from a killed browser made a call with one person show **2/2**. - With small limits (2–5), one such user makes the channel look full. The header counter (`RoomViewHeader`, "2/2") is wrong the same way. **Fix:** count distinct users, matching the guard: - `current = new Set(callMembers.map((m) => m.sender)).size`; - `channelFull = maxUsers > 0 && !alreadyMember && current >= maxUsers`. Add a unit test with two memberships from one user. Optionally show "2 people (3 devices)" in the tooltip. **Bob sees "Channel Full (2/2)" with one person (two devices) in the call** ![voice-devices-bob.png](https://code.lotusguild.org/attachments/121a3c68-611d-4b05-bd8a-0fc970254cbd) --- _Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny `30e2dbcd`, cinny-desktop `8f67ec7`, element-call `3b0050e8`._
jared added the bugpriority: mediumarea: calls labels 2026-10-10 22:15:43 -04:00
Author
Owner

Server side of the same limit: LotusGuild/matrix#25. The guard counts distinct users (correct), but only at token issue, so simultaneous joins can exceed it.

Server side of the same limit: LotusGuild/matrix#25. The guard counts distinct users (correct), but only at token issue, so simultaneous joins can exceed it.
jared closed this issue 2026-10-11 22:57:38 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#342