bitter-analyst-30633
04/10/2026, 12:22 PMtall-airline-16756
04/10/2026, 12:23 PMtall-airline-16756
04/10/2026, 12:23 PMcreateOffer() (part of SDP negotiation when publishing or restarting media) fails. WebRTC disallows creating offers on a closed connection.
Common causes
• The Room or participant disconnected and your app tried to publish or renegotiate afterward.
• A reconnection attempt raced with a publish/track restart while the underlying RTCPeerConnection was closed.
• Rapid connect/disconnect cycles or improper cleanup left the connection in a closed state.
How to fix it
• Only publish tracks or start negotiations when the Room is connected. Listen for RoomEvent.ConnectionStateChanged and act when state === ConnectionState.Connected.
• If the connection was lost, wait for the SDK to finish reconnecting or call room.connect() again before publishing.
• Avoid publishing or restarting tracks during reconnect/rejoin flows; add guards to prevent concurrent negotiation calls.
If you want, share:
• whether this happens after a network drop or during normal publish,
• which SDK version you’re using,
• a short snippet showing how you call publish/restart —
and I’ll suggest the specific code change.
Sources: client-sdk-js | livekit/livekit_composite | DeepWiki, with interactive elementsbitter-analyst-30633
04/10/2026, 12:30 PMtall-airline-16756
04/10/2026, 12:30 PMConnectionState.Connected (or wait for the SDK reconnection) to avoid calling createOffer on a closed connection.
• If you share logs/snippet, I’ll point to the exact change to prevent the race., with interactive elementsbitter-analyst-30633
04/10/2026, 12:32 PMtall-airline-16756
04/10/2026, 12:32 PMRoomEvent.ConnectionStateChanged or checks for ConnectionState.Connected).
• Does this happen every time with the failing URL or intermittently (e.g., after a network drop or reconnect)?
With those I can point to the exact fix (connection guards, reorder, or token/host checks).
, with interactive elementsbitter-analyst-30633
04/10/2026, 12:34 PMtall-airline-16756
04/10/2026, 12:34 PMlivekit-client@2.15.5 and livekit-server-sdk@2.9.7.
Quick checklist first (these are the most common causes):
• Ensure you only call publish/renegotiate after ConnectionState.Connected.
• Avoid publishing while the SDK is reconnecting or immediately after a disconnect — add guards so createOffer() isn’t invoked on a closed RTCPeerConnection.
• Check console for earlier signaling errors (authentication, 4xx/5xx, or WebSocket closure) which can close the peer connection before negotiation.
To diagnose quickly, please share:
1. A short code snippet showing how you call room.connect() and when you call room.localParticipant.publishTrack() (or publish()), including any RoomEvent.ConnectionStateChanged handlers.
2. The exact console logs (a few lines before and after) when using the failing URL.
3. Does the failure happen every connection attempt to the failing host or only after a network drop/reconnect?
Send those and I’ll point to the precise code change (connection guard or ordering) to stop the “could not createOffer with closed peer connection” error.
, with interactive elementsbitter-analyst-30633
04/10/2026, 12:37 PMroom.connect() is called:
We use <LiveKitRoom> from @livekit/components-react — we do not call room.connect() manually. The component's internal useLiveKitRoom hook calls room.connect(serverUrl, token) in a useEffect. We pass connect={true} and audio={true}.
How and when publishing happens:
We do not call room.localParticipant.publishTrack() manually either. Looking at the useLiveKitRoom hook source, publishing is triggered automatically inside onSignalConnected (the RoomEvent.SignalConnected handler), which calls localParticipant.setMicrophoneEnabled(true) because we pass audio={true}. So the sequence LiveKit controls is:
RoomEvent.SignalConnected → setMicrophoneEnabled(true) → createOffer
There is no RoomEvent.ConnectionStateChanged guard on our side — it's all inside the useLiveKitRoom hook.
Does it happen every attempt or only after reconnect?
It happens on the first connection attempt to the production endpoint — no prior disconnect or network drop is needed to reproduce it.
The specific endpoints:
• Fails: <wss://samora-prod-up2gn0dr.livekit.cloud> (LiveKit Cloud hosted)
• Works: Local agent (same browser, same code, same token logic)
Our hypothesis based on reading `useLiveKitRoom.ts`:
RoomEvent.SignalConnected fires when the WebSocket signal channel is up — at that point setMicrophoneEnabled(true) runs and triggers createOffer internally. With the production cloud endpoint, something is closing the RTCPeerConnection between SignalConnected and the time createOffer actually executes (this window is larger in production due to higher RTT and TURN ICE gathering time). The peer connection ends up in closed state before createOffer completes.
Specific question for LiveKit: Is the production server sending a server-side LEAVE or connection rejection (e.g. due to agent room not being ready, or TURN credential validation) during the ICE phase that closes the peer connection while setMicrophoneEnabled is still awaiting renegotiation? Is there a RoomConnectOptions.rtcConfig or iceTransportPolicy flag we can use to control TURN behavior, or a way to delay audio publishing until RoomEvent.Connected (full ICE + connected) rather than RoomEvent.SignalConnected?tall-airline-16756
04/10/2026, 12:38 PMSignalConnected and when setMicrophoneEnabled(true) triggers createOffer.
Short answer
• SignalConnected fires when the signaling WebSocket is up — this happens before ICE gathering/connection. That means setMicrophoneEnabled(true) can trigger an SDP offer while ICE is still in progress.
• The SDK/Cloud can close the peer connection during ICE (for example if the server rejects the session or TURN/auth fails), which causes the “could not createOffer with closed peer connection” error.
• There is no built‑in flag to delay the automatic audio publish until RoomEvent.Connected.
What you can do now (practical options)
• Disable automatic audio publishing and enable it after full connection:
◦ Remove audio={true} from <LiveKitRoom> so it does not call setMicrophoneEnabled on SignalConnected.
◦ After you receive RoomEvent.Connected (or verify room.connectionState === ConnectionState.Connected), call localParticipant.setMicrophoneEnabled(true) yourself.
• Keep automatic audio but add a short delay/guard in your app (less ideal):
◦ Wrap enabling microphone in logic that checks room.connectionState === ConnectionState.Connected and retries a few times if not.
• Force relay/turn behavior if you suspect UDP/firewall issues:
◦ Supply an rtcConfig with iceTransportPolicy: 'relay' so candidates prefer TURN. This can reduce failure due to blocked UDP, but may increase latency and won’t help if the server is rejecting the session for another reason.
• Collect targeted logs for further diagnosis:
◦ Console lines around the error (signaling messages, websocket close codes).
◦ Server-side logs or any HTTP/WS 4xx/5xx seen during connect.
◦ Whether TURN credentials were issued and ICE candidate types (host/srflx/relay).
If you want, I can:
• Show the minimal change to stop automatic publishing and enable mic after Connected.
• Review your console/WS logs you paste here to spot a server-side rejection or ICE failure pattern.
Which would you like me to do next?
Sources: React Core and Components | LiveKit | React Core and Components | LiveKit | Client SDKs | livekit/livekit_composite | DeepWiki | How we built a globally distributed mesh network to scale WebRTC | LiveKit | client-sdk-js | livekit/livekit_composite | DeepWiki, with interactive elementsbitter-analyst-30633
04/10/2026, 12:41 PMtall-airline-16756
04/10/2026, 12:41 PM*.omumbai1a.production.livekit.cloud), the room/participant presence, signaling, and media for that session are registered and handled by that region’s LiveKit deployment. Metrics, logs, and any region-scoped features (recordings, routing) will be tied to that region.
• If you connect via a generic/load‑balanced LiveKit URL (the non‑regional host), the request can be routed to any region according to the load balancer or service routing. The session will be registered in whichever region the load balancer forwards the connection to.
Practical notes:
• Tokens must match the host/region you connect to — issue tokens for the same URL the agent uses.
• If you need an agent to always operate in a specific region (for latency, compliance, or resource locality), point it at that region’s URL rather than the generic endpoint.
• Logs/metrics and any region-scoped features will appear under the region that served the connection.
If you want, tell me which agent workflows (rooms, recordings, or analytics) you care about and I’ll confirm how they map to region deployments.
, with interactive elementsbitter-analyst-30633
04/10/2026, 12:43 PMtall-airline-16756
04/10/2026, 12:43 PM*.omumbai1a.production.livekit.cloud is treated as a best‑effort routing hint. Your agent’s session will usually be routed to that region, but there is no contractual or technical guarantee that all processing or storage will remain in‑region.
• Observability data (logs, traces, recordings) is processed/stored in the U.S. by default, regardless of selected region, unless you have region pinning enabled via Scale.
• If you require strict data residency or guaranteed regional processing, you will need the Scale plan (or higher) and region pinning enabled for your project.
If you want, tell me which artifacts you need pinned (media, recordings, logs, inference) and I’ll outline the exact mapping and next steps to enable guaranteed region pinning.
Sources: Building Performant Voice Agents in India | LiveKit | Building Performant Voice Agents in India | LiveKit | Checklist for Regional Deployments | LiveKit | Checklist for Regional Deployments | LiveKit | Data Processing Addendum | LiveKit, with interactive elementsbitter-analyst-30633
04/10/2026, 12:45 PMtall-airline-16756
04/10/2026, 12:46 PMcreateOffer.
• Network/firewall differences: production endpoints may require TURN (TCP/443) and can experience longer ICE gathering times; blocked UDP or misconfigured TURN leads to failures.
Please provide these so I can diagnose:
1. Console/Network logs (a few lines before/after the error) when connecting to the generic URL.
2. Decoded token claims (redact secrets) — show aud, iss, exp, kid.
3. Whether the failure is consistent on first attempt or intermittent.
4. Your exact connect URL string (paste it) and any WS close codes you see.
Immediate debugging steps to try
• Enable verbose SDK logging in the browser and reproduce; paste relevant logs.
• Inspect ICE candidates in the browser network/rtc internals and note whether you see only host candidates or also relay (TURN).
• Try forcing iceTransportPolicy: 'relay' in your rtc config to see if TURN fixes it.
• Temporarily disable automatic audio publish (remove audio={true}) and manually call setMicrophoneEnabled(true) after RoomEvent.Connected to rule out the Signal→ICE race.
Send the logs + token claims and I’ll point to the exact root cause and the minimal change to fix it.
, with interactive elementsbitter-analyst-30633
04/10/2026, 12:51 PMtall-airline-16756
04/10/2026, 12:51 PMcustomer_cancelled cancellations and how this may tie to region routing.
What’s likely happening
• SIP calls (via Plivo) can be cancelled by LiveKit if the session is routed to a region that can’t reach the SIP gateway or if signaling/ICE fails during setup. Using the generic (load‑balanced) URL can route the connection to any region; on non‑Scale plans region residency is best‑effort only.
• If the LiveKit instance handling the call cannot reach Plivo (network/ firewall/TURN differences) or if TURN credentials/ICE fail, the server may terminate the call, which appears as customer_cancelled from Plivo’s side.
Actions to take (prioritized)
1. Confirm routing and tokens
◦ Verify the join token’s `aud`/`iss` exactly matches the host you connect to.
◦ If you need consistent SIP behavior, point the agent to the specific region URL (e.g., *.omumbai1a.production.livekit.cloud) so the call is handled in that region (note: strict region pinning requires Scale plan).
1. Check network reachability
◦ Ensure the LiveKit region can reach Plivo’s SIP endpoint (firewall rules, outbound ports, and any IP allowlists).
◦ Confirm TURN was issued and whether ICE candidates include relay candidates (verify in browser rtc internals or logs).
1. Capture logs
◦ Enable verbose LiveKit SDK logs and capture signaling/WS messages during a failing SIP call.
◦ Collect Plivo SIP logs and any SIP response codes around the customer_cancelled event.
1. Temporary mitigations
◦ Force iceTransportPolicy: 'relay' to force TURN if you suspect UDP is blocked.
◦ If automatic region routing is causing failures and you’re not on Scale, try targeting a regional endpoint that you know can reach Plivo.
What I need from you to diagnose further
• The exact LiveKit URL you’re using (generic or regional).
• Plivo SIP error/response logs around the cancellation.
• LiveKit client/server logs (signaling WS close codes, ICE candidate types).
• Whether you’re on Scale or a lower plan.
If you share those logs/values I’ll analyze the sequence and point to the precise fix (region routing change, token fix, firewall rule, or ICE/TURN adjustment).
, with interactive elements