Reference · Help and examples
Troubleshooting
Find the next useful check when a client connects but sees no events, replay stalls, or native media fails.
Start at the first boundary that failed: bootstrap, room authorization, durable delivery, application processing, or native media. A healthy WebSocket does not prove that a selector matches; a successful room event does not prove that a microphone track is playing.
Connected, but no events
Check the channel ID, event type, and participant grants separately. A channelTypes filter needs a matching channel definition. Selector dimensions combine with AND. For event access, the resource is the channel ID when present, otherwise the event type. Visibility must also match.
Inspect the subscription's state and cursor. A checkpoint that advances while no event arrives means the server scanned history but your authorized selection yielded no records. live only acknowledges subscription setup. Start with the returned event from a known publication and compare its channel and type to your selector.
Relevant code: session selection, selector rules, visibility.
History is empty, but later events exist
room.events() reads one bounded scanned page. Read X-RMC-Cursor through the history helper or use a subscription. Do not stop solely because events.length === 0; the first 1,000 room records could all be hidden.
If the helper reports a missing header in a cross-origin browser, the current CORS handler does not expose that header. Use a same-origin/server-side reader or the SDK stream. This is different from an empty authorized page.
A consumer closes or falls behind
Look for a rejected onEvent / onCheckpoint promise or the SDK's bounded-queue overflow error. Move expensive presentation work after a small durable projection update, or reduce the consumer's selection. Do not add an unbounded queue to hide the problem.
Recover explicitly from the closed handle's cursor after addressing the cause. Transport errors can be transient; check closed or onStateChange before recreating everything. An event handler already in progress may finish after a disconnect or close, so guard stale view updates and make repeated effects safe.
Relevant code: SDK stream, stream tests.
Messages repeat or disappear after reload
For repeats, deduplicate by event ID or apply monotonic resource versions. For missing history, verify that you restored the projection associated with your saved cursor. A cursor without its model is not a resumable application state.
Scope saved progress by room, participant, and selector/projection version. A wider selector or new identity can require an earlier replay position. If hydrating from a snapshot, tolerate overlap: its current multi-query implementation can include resource versions newer than last_seq.
Follow the projection recipe.
Publication fails or a retry returns old content
A 403 usually means the participant lacks event:publish for the effective resource. A 409 can indicate an incompatible reuse of a room-wide client event ID. Reusing an accepted ID does not update the event: for authorized readers, compatible retries return the original publication even if the new payload differs.
Create the retry ID once per user action and preserve the original body. Use a new action/event for an edit. Do not supply actor identity to the publication API; the server derives it from authentication.
Relevant code: publication, publication tests.
Browser REST works, but the stream does not
Check the URL scheme (wss behind HTTPS), proxy WebSocket upgrade support, origin, and initial version-2 authentication frame. A bare browser WebSocket does not send the SDK's authentication handshake for you.
REST CORS and WebSocket origin validation are separate. The demo's RMC_ALLOWED_ORIGINS configuration does not imply arbitrary WebSocket origins are accepted, and the standalone binary does not read that environment variable. See browser setup.
Camera or screen share connects without tracks
Confirm that the channel is an open media.video input with the expected LiveKit source binding. An observation topic created with createVideoChannel() cannot substitute for a camera publishing channel. Inspect the credential's publish_bindings, then inspect the native session's local track state.
An explicit empty publishChannelIds list disables both publishing and subscribing. A scoped camera token also disables subscribing; use the correct receive-only participant/token path for a processor. Check provider configuration and the existing presence refresh before blaming durable pub/sub.
Relevant code: media token handler, LiveKit adapter, media tests.
Voice stays active after leaving the view
room.dispose() only releases the local SDK stream. Close the owned VoiceConnection or abort its lifetime signal; disconnect owned LiveKit resources separately. Cleanup must cover pending voice negotiation too, not only a connection already assigned to a UI variable.
The current voice SDK owns microphone/peer cleanup across negotiation and reconnect. Use its lifetime signal rather than maintaining an independent partial cleanup path. See voice.ts and voice cleanup tests.
Room close or revoke returns a provider error
A 502 can occur after local revocation or closure committed. RMC has queued durable provider cleanup and will retry; do not assume that the local permission change rolled back. Check basic server logs and provider reachability. Already issued provider tokens have their own expiry behavior.
The LiveKit dependency guide distinguishes RMC authority from provider-token revocation. Use the FAQ for scope and recovery questions, including partial transcripts, agent restart, and provider replacement.