sparse-rain-90593
12/31/2025, 11:13 PMtall-airline-16756
12/31/2025, 11:13 PMtall-airline-16756
12/31/2025, 11:13 PM/twirp/livekit.RoomService/DeleteRoom.
2. Check any Agent code, tools, or shutdown hooks for `delete_room()`/`room.delete_room()` calls.
3. Inspect webhook handlers (e.g., on participant_left) and any cleanup/cron scripts that might delete rooms.
4. Correlate timestamps: match the room_finished webhook time with any backend task logs around the same time.
If you want, tell me:
• whether you use Agents, webhooks, or scheduled cleanup jobs,
• and a recent room ID and timestamp—
and I’ll suggest targeted places to look in your code/logs and next steps to prevent unintended deletes.
Sources: Job lifecycle | LiveKit docs | Server-side | livekit/livekit_composite | DeepWiki | Avoiding Room ID Reuse When Re-Creating Rooms With the Same Name | server-sdk-js | livekit/livekit_composite | DeepWiki | Agents telephony integration | LiveKit docssparse-rain-90593
12/31/2025, 11:16 PMtall-airline-16756
12/31/2025, 11:16 PM/twirp/livekit.RoomService/DeleteRoom around Dec 31, 2025 5:49 PM (5 minutes after creation).
2. Inspect webhook handlers again for any indirect delete behavior — e.g., a handler that on participant_left or another event triggers code path that eventually calls DeleteRoom, or throws an error that a retry/cleanup routine interprets as a signal to delete.
3. Check any admin/UI actions or automation (CI, deploy hooks, admin dashboards) that might call the API — include API keys used and who/what has access.
4. Verify your backend error logs and crash/restart hooks: some services call DeleteRoom during shutdown or error recovery.
5. If you have multiple services/environments, confirm the request source by looking for request metadata (client IP, API key, user id) in logs to identify which service made the DeleteRoom call.
If you’d like, share:
• any webhook event logs around the room end time,
• the server log snippet showing requests at that timestamp,
• or which API key/client might have been active,
and I’ll help interpret the logs and point to the exact code path likely issuing the DeleteRoom.sparse-rain-90593
12/31/2025, 11:21 PMtall-airline-16756
12/31/2025, 11:22 PMparticipant_left event reason(s) and timestamps for that room (what reason appears — e.g., CLIENT_INITIATED, USER_REJECTED, ROOM_DELETED).
3. Any room creation options you set (e.g., close_on_disconnect, delete_room_on_close, or similar RoomOptions).
4. Whether you create any egress, SIP calls, or use any short-lived service connections for this room.
If you share the webhook log or the participant_left reason and timestamps, I’ll analyze them and point to the exact likely trigger and the next remediation (e.g., disable auto-close, keep a dummy participant, or adjust your egress/hangup logic).
Sources: Agent session | LiveKit docs | Server lifecycle | LiveKit docs | Agents telephony integration | LiveKit docssparse-rain-90593
12/31/2025, 11:28 PMfunc (core Context) CreateJoinToken(roomName string, identity string) (JoinStudioResponse, error) {
token := auth.NewAccessToken(core.LiveKitConfig.Key, core.LiveKitConfig.Secret)
grant := &auth.VideoGrant{
RoomJoin: true,
Room: roomName,
RoomAdmin: true,
Recorder: true,
}
token.AddGrant(grant).SetIdentity(identity).SetValidFor(kLivekitTokenLifetime)
tokenStr, err := token.ToJWT()
if err != nil {
return JoinStudioResponse{}, err
}
response := JoinStudioResponse{
Token: tokenStr,
RoomName: roomName,
HostIdentity: identity,
}
return response, nil
}
Maybe RoomCreate - should be true for the particpant..?
4. We do create egress for the roomtall-airline-16756
12/31/2025, 11:28 PMROOM_CLOSED, your token grants include Recorder: true, and you do create egress. That strongly narrows it down.
Short diagnosis
• ROOM_CLOSED means the room was deleted server-side (same outcome as DeleteRoom). Deleting the room can happen indirectly when the last non-agent participant and any recorder/egress participants finish, or if room options instruct auto-close/delete on participant disconnect or egress finish.
• Because you enable the Recorder grant and you create egress, the recorder/egress lifecycle is the prime suspect: if egress finishes or is configured to stop the session (or your egress creation requests include flags that delete the room on completion), LiveKit can delete the room which produces ROOM_CLOSED.
Next steps — please provide two things so I can pinpoint and give the exact fix:
1. The egress creation code/parameters you use (especially any flags like stop_on_exit, stop_on_finish, or delete_room_on_finish).
2. Any RoomOptions you set when creating/joining the room (e.g., close_on_disconnect / delete_room_on_close).
What I’ll check once you share those
• Whether egress completion is triggering a room delete and which option causes it.
• If the Recorder grant + egress behavior requires changing token grants, keeping a short-lived “keeper” participant, or adjusting egress flags / room options.
If you prefer, paste the egress request JSON or code snippet and your room creation/join options and I’ll point to the exact field to change.sparse-rain-90593
12/31/2025, 11:32 PMfunc (core Context) StartTrackCompositeRecording(roomName string, assetId string, projectId string, userId string, audioTrackId string, videoTrackId string, trackTitle string, storageBucketName, languageCode, participantSid string, isHost bool) (string, error) {
trackCompositeId := GenerateTrackID()
filenameForInitialUpload := KPubsubMediaTypeLivekitRecording + "-" + assetId + "-" + trackCompositeId
request := &livekit.TrackCompositeEgressRequest{
RoomName: roomName,
AudioTrackId: audioTrackId,
VideoTrackId: videoTrackId,
Output: &livekit.TrackCompositeEgressRequest_File{
File: &livekit.EncodedFileOutput{
FileType: livekit.EncodedFileType_MP4,
Filepath: filenameForInitialUpload,
Output: core.GetLivekitEgressOutput(storageBucketName),
},
},
}
egressInfo, err := core.LivekitEgressClient.StartTrackCompositeEgress(core.BackgroundContext, request)
if err != nil {
newErrMsg := "Error starting track composite egress: " + err.Error()
return "", errors.New(newErrMsg)
}
err = core.CreateCloudRecordingEntry(trackCompositeId, assetId, egressInfo.EgressId, roomName, participantSid, isHost)
if err != nil {
newErrMsg := "Error creating cloud recording entry: " + err.Error()
return "", errors.New(newErrMsg)
}
recordingType := TrackComposite
fileExt := ".mp4"
_, err = core.CreateTrack(trackCompositeId, assetId, projectId, userId, egressInfo.EgressId, trackTitle, recordingType, "Recording in progress", storageBucketName, trackCompositeId+fileExt, languageCode, false)
if err != nil {
newErrMsg := "Error creating track composite: " + err.Error()
return "", errors.New(newErrMsg)
}
return egressInfo.EgressId, nil
}
Why would egress complete - we don't call stop egress until the host participant leaves - and the logs show the participant never left until the ROOM_CLOSED trigger disconnecttall-airline-16756
12/31/2025, 11:33 PMROOM_CLOSED participant_left reason even though you never call DeleteRoom directly.
What to check and quick actions to try
• Remove the Recorder: true grant from the token and issue recorder/egress with a separate token if you don't need the hidden recorder as a participant.
• Confirm egress options/flags: ensure you're not setting any "stop on finish" or "delete room on finish" behavior in the egress request (your code above doesn't show such flags, but check any defaults in your egress client).
• Keep the host (or a lightweight keep-alive bot) connected while egress runs so the room isn't empty when the recorder participant leaves.
• Verify you don't have room options enabled that auto-delete/close on disconnect (e.g., close_on_disconnect / delete-on-empty).
Please tell me: 1) Do you need the Recorder grant on the user's token, or can recorder/egress use a separate token? 2) Can you share any egress/default room option configuration (or confirm none are set)?
If you want, I can recommend the exact minimal change (remove Recorder: true or keep a dummy participant) once you confirm those two items.sparse-rain-90593
12/31/2025, 11:35 PMtall-airline-16756
12/31/2025, 11:35 PMRecorder: true.
To clarify, and to confirm next steps:
• Egress started from your backend using the server SDK is authenticated with your LiveKit API key/secret, not a participant JWT. You do not need a special token for egress when you call it from the backend.
• The Recorder grant on a participant token creates a hidden recorder participant in the room. If that recorder/egress participant leaves or finishes and the room is configured to close on recorder/egress completion (or becomes empty), it can produce the ROOM_CLOSED disconnect you saw.
• Removing Recorder: true from the user token should prevent creating that recorder participant on join and reduce the chance of the recorder triggering a room delete. No separate recorder token is required for your backend egress calls.
Two quick checks to finish:
1. Confirm you’ve removed Recorder: true from issued tokens and redeployed.
2. Verify room options aren’t set to auto-delete on empty/egress finish (e.g., close_on_disconnect / delete-on-empty flags).
If the ROOM_CLOSED still occurs after that, please share the webhook events around the room end (participant_left reasons and timestamps) and the egress finished/egress stopped webhook — I’ll analyze them and point to the exact trigger.
Sources: Python SDKs | livekit/livekit_composite | DeepWiki | server-sdk-js | livekit/livekit_composite | DeepWiki | server-sdk-ruby | livekit/livekit_composite | DeepWiki | Agent dispatch | LiveKit docssparse-rain-90593
12/31/2025, 11:46 PMfunc (core Context) CreateJoinTokenForGuest(roomName string, identity string) (JoinStudioResponse, string, error) {
token := auth.NewAccessToken(core.LiveKitConfig.Key, core.LiveKitConfig.Secret)
grant := &auth.VideoGrant{
RoomJoin: true,
Room: roomName,
RoomCreate: false,
}
participantIdentifier := GenerateParticipantIdentifier()
token.AddGrant(grant).SetIdentity(identity).SetValidFor(kLivekitTokenLifetime)
tokenStr, err := token.ToJWT()
if err != nil {
return JoinStudioResponse{}, "", err
}
response := JoinStudioResponse{
Token: tokenStr,
RoomName: roomName,
ParticipantIdentifier: participantIdentifier,
}
return response, participantIdentifier, nil
}tall-airline-16756
12/31/2025, 11:47 PMRoomJoin: true and RoomCreate: false — that’s correct for guests who should only join existing rooms.
• No `Recorder`/recording grant is present, so this token will not create a hidden recorder participant that could trigger ROOM_CLOSED.
• Token lifetime is applied via SetValidFor(kLivekitTokenLifetime) — ok as long as that value matches your session expectations.
• You generate a separate participantIdentifier, which is fine for your app logic.
Recommendations (if needed)
• If guests must publish or subscribe, explicitly set those fields on the VideoGrant (e.g., CanPublish, CanSubscribe) rather than relying on defaults.
• Keep tokens scoped minimal (avoid room admin/recorder unless required).
• If you need very short sessions, reduce `kLivekitTokenLifetime`; for long-lived sessions consider a refresh flow.
This token config should not cause ROOM_CLOSED events. Do you want me to suggest exact VideoGrant fields for a typical publishing guest (mic/camera) or review any other token flows?sparse-rain-90593
01/01/2026, 6:13 AMtall-airline-16756
01/01/2026, 6:13 AMRoomJoin: true (allow joining)
• RoomCreate: false or omit it (prevent creating)
• Do NOT set RoomAdmin: true or Recorder: true
• Explicitly set publish/subscribe capabilities if needed (e.g., allow mic/camera): set CanPublish / CanPublishData / CanSubscribe on the VideoGrant as appropriate
Example Go code (adjust `CanPublish`/`CanSubscribe` to match your guest permissions):
token := auth.NewAccessToken(core.LiveKitConfig.Key, core.LiveKitConfig.Secret)
grant := &auth.VideoGrant{
RoomJoin: true,
RoomCreate: false, // prevents creating new rooms
// CanPublish: true, // enable if guests should publish audio/video
// CanSubscribe: true, // enable if guests should receive tracks
}
token.AddGrant(grant).SetIdentity(identity).SetValidFor(kLivekitTokenLifetime)
tokenStr, _ := token.ToJWT()
Notes and best practices
• Keep tokens minimal: only include the permissions guests actually need.
• Never include RoomAdmin or Recorder in guest tokens.
• Room creation control is enforced server-side by the RoomCreate flag in the grant; guests with RoomCreate: false cannot create rooms by joining.
• If you need further granularity, explicitly set `CanPublish`/`CanSubscribe` instead of relying on defaults.
If you want, tell me what guest capabilities you want (e.g., mic + camera only, or view-only) and I’ll give the exact grant fields.