In an audio-only voice room (io.lotus.room_quality: { allow_camera: false, allow_screenshare: false }), the voice-limit guard re-signs the LiveKit token with video.canPublishSources: ["microphone"] (verified by decoding the JWT; open rooms get no canPublishSources restriction). startLotusAudioInject (src/lotus/lotusAudioInject.ts) publishes each clip as a separate Unknown-source track (room.localParticipant.publishTrack(clone, …)), and LiveKit refuses it:
insufficient permissions to publish {room: !cwFd…, participant: @aud_main:localhost:HIEQ…}
[lotus] inject_audio: publish failed PublishTrackError: failed to publish track, insufficient permissions
Measured: Bob's live remote audio tracks during the clip stay at 1 (in an open room they go 1 → 2 → 1). The host shows nothing: no toast, the button just stops "playing". Audio-only voice channels are exactly where a soundboard is most likely to be used.
Fix options:
Inject through the microphone source: mix the clip into the published mic track with a WebAudio MediaStreamAudioDestinationNode fed by mic + clip, after the denoise processor so the clip isn't "denoised". This also makes it subject to mute, which is the stated intent (#13).
Or have the guard allow a dedicated soundboard source. Simply adding unknown would also let a client publish video as unknown and bypass the camera block, so don't.
Either way, reply { played: false, reason: "not_permitted" } and have the host say "The soundboard isn't available in this room" rather than failing silently.
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.
In an **audio-only** voice room (`io.lotus.room_quality: { allow_camera: false, allow_screenshare: false }`), the voice-limit guard re-signs the LiveKit token with `video.canPublishSources: ["microphone"]` (verified by decoding the JWT; open rooms get no `canPublishSources` restriction). `startLotusAudioInject` (src/lotus/lotusAudioInject.ts) publishes each clip as a **separate `Unknown`-source track** (`room.localParticipant.publishTrack(clone, …)`), and LiveKit refuses it:
```
insufficient permissions to publish {room: !cwFd…, participant: @aud_main:localhost:HIEQ…}
[lotus] inject_audio: publish failed PublishTrackError: failed to publish track, insufficient permissions
```
**Measured:** Bob's live remote audio tracks during the clip stay at **1** (in an open room they go 1 → 2 → 1). The host shows **nothing**: no toast, the button just stops "playing". Audio-only voice channels are exactly where a soundboard is most likely to be used.
**Fix options:**
- Inject through the **microphone source**: mix the clip into the published mic track with a WebAudio `MediaStreamAudioDestinationNode` fed by mic + clip, after the denoise processor so the clip isn't "denoised". This also makes it subject to mute, which is the stated intent (#13).
- Or have the guard allow a dedicated soundboard source. Simply adding `unknown` would also let a client publish *video* as unknown and bypass the camera block, so don't.
- Either way, reply `{ played: false, reason: "not_permitted" }` and have the host say "The soundboard isn't available in this room" rather than failing silently.
---
_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`._
Scope is wider than audio-only. The guard's allowed_sources() (matrix livekit/voice-limit-guard.py:148-164) returns a narrowed list whenever eitherallow_camera or allow_screenshare is false (e.g. [microphone, screen_share, screen_share_audio] for a camera-off room). The Unknown-source clip track is therefore refused in any room with a call policy, not just fully audio-only ones. Screen-share audio is fine: it's included whenever screenshare is allowed.
Scope is wider than audio-only. The guard's `allowed_sources()` (matrix `livekit/voice-limit-guard.py:148-164`) returns a narrowed list whenever **either** `allow_camera` or `allow_screenshare` is false (e.g. `[microphone, screen_share, screen_share_audio]` for a camera-off room). The Unknown-source clip track is therefore refused in **any** room with a call policy, not just fully audio-only ones. Screen-share audio is fine: it's included whenever screenshare is allowed.
Partly done: #52 (released in lotus.25) refuses up front with not_permitted, and cinny #415 tells the user the soundboard isn't available in that voice channel. Still open: actually playing clips in audio-only rooms by mixing them into the published mic track.
🤖 Claude Code
Partly done: #52 (released in lotus.25) refuses up front with `not_permitted`, and cinny #415 tells the user the soundboard isn't available in that voice channel. Still open: actually playing clips in audio-only rooms by mixing them into the published mic track.
🤖 Claude Code
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
In an audio-only voice room (
io.lotus.room_quality: { allow_camera: false, allow_screenshare: false }), the voice-limit guard re-signs the LiveKit token withvideo.canPublishSources: ["microphone"](verified by decoding the JWT; open rooms get nocanPublishSourcesrestriction).startLotusAudioInject(src/lotus/lotusAudioInject.ts) publishes each clip as a separateUnknown-source track (room.localParticipant.publishTrack(clone, …)), and LiveKit refuses it:Measured: Bob's live remote audio tracks during the clip stay at 1 (in an open room they go 1 → 2 → 1). The host shows nothing: no toast, the button just stops "playing". Audio-only voice channels are exactly where a soundboard is most likely to be used.
Fix options:
MediaStreamAudioDestinationNodefed by mic + clip, after the denoise processor so the clip isn't "denoised". This also makes it subject to mute, which is the stated intent (#13).unknownwould also let a client publish video as unknown and bypass the camera block, so don't.{ played: false, reason: "not_permitted" }and have the host say "The soundboard isn't available in this room" rather than failing silently.Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny
30e2dbcd, cinny-desktop8f67ec7, element-call3b0050e8.Scope is wider than audio-only. The guard's
allowed_sources()(matrixlivekit/voice-limit-guard.py:148-164) returns a narrowed list whenever eitherallow_cameraorallow_screenshareis false (e.g.[microphone, screen_share, screen_share_audio]for a camera-off room). The Unknown-source clip track is therefore refused in any room with a call policy, not just fully audio-only ones. Screen-share audio is fine: it's included whenever screenshare is allowed.Partly done: #52 (released in lotus.25) refuses up front with
not_permitted, and cinny #415 tells the user the soundboard isn't available in that voice channel. Still open: actually playing clips in audio-only rooms by mixing them into the published mic track.🤖 Claude Code