{"record":{"id":"5b42eb0ddf3204cd","repo":"RocketChat/Rocket.Chat","slug":"invalid-call-5b42eb","errorCode":null,"errorMessage":"invalid-call","messagePattern":"invalid-call","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/services/video-conference/service.ts","lineNumber":148,"sourceCode":"\t\t\tif (data.type === 'videoconference') {\n\t\t\t\tdata.title = title;\n\t\t\t}\n\n\t\t\treturn this.create(data, false);\n\t\t}).catch((err) => {\n\t\t\tlogger.error({\n\t\t\t\tname: 'Error on VideoConf.start',\n\t\t\t\terr,\n\t\t\t});\n\t\t\tthrow err;\n\t\t});\n\t}\n\n\tpublic async join(uid: IUser['_id'] | undefined, callId: VideoConference['_id'], options: VideoConferenceJoinOptions): Promise<string> {\n\t\treturn wrapExceptions(async () => {\n\t\t\tconst call = await VideoConferenceModel.findOneById(callId);\n\t\t\tif (!call || call.endedAt || !videoConfTypes.isCallManagedByApp(call)) {\n\t\t\t\tthrow new Error('invalid-call');\n\t\t\t}\n\n\t\t\tlet user: Pick<IUser, '_id' | 'username' | 'name' | 'avatarETag'> | null = null;\n\n\t\t\tif (uid) {\n\t\t\t\tuser = await Users.findOneById<Pick<IUser, '_id' | 'username' | 'name' | 'avatarETag'>>(uid, {\n\t\t\t\t\tprojection: { name: 1, username: 1, avatarETag: 1 },\n\t\t\t\t});\n\t\t\t\tif (!user) {\n\t\t\t\t\tthrow new Error('failed-to-load-own-data');\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (call.providerName === 'jitsi') {\n\t\t\t\tupdateCounter({ settingsId: 'Jitsi_Click_To_Join_Count' });\n\t\t\t}\n\n\t\t\treturn this.joinCall(call, user || undefined, options);","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/e4b8178b205510181a96ceefee043d0abcd13e5a/apps/meteor/server/services/video-conference/service.ts#L130-L166","documentation":"Thrown by VideoConferenceService.join when the call cannot be joined: findOneById returns no document, the call already has endedAt set, or videoConfTypes.isCallManagedByApp(call) is false (the call's provider is not app-managed, so joining goes through the provider URL instead). All three states collapse into this single error, so inspect the call document to distinguish them. The user lookup happens after, so this is the first join guard.","triggerScenarios":"POST video-conference.join with an expired, declined, or already-ended call id; joining after the ringing timeout; reusing a call id whose provider is not app-managed (join must use call.url instead); mistyped callId.","commonSituations":"Callee opens the ringing popup after the caller cancelled; two devices race to accept the same call; stale call ids left in UI state after navigation.","solutions":["Before joining, load the call and require !call.endedAt && videoConfTypes.isCallManagedByApp(call)","Treat invalid-call on join as 'call over': dismiss the ringing modal and stop timers — do not blind-retry","Use fresh call ids from the call message / callStarted events rather than stored ones","If the provider is not app-managed, join via the call's url instead of the join endpoint"],"exampleFix":"// before\nconst url = await VideoConfService.join(uid, callId, options);\n\n// after\nconst call = await VideoConferenceModel.findOneById(callId);\nif (!call || call.endedAt || !videoConfTypes.isCallManagedByApp(call)) {\n  ui.dismissRinging();\n  throw new Error('invalid-call');\n}\nconst url = await VideoConfService.join(uid, callId, options);","handlingStrategy":"try-catch","validationCode":"const call = await VideoConferenceModel.findOneById(callId);\nif (!call || call.endedAt || !videoConfTypes.isCallManagedByApp(call)) {\n  throw new Meteor.Error('invalid-call', 'Call is gone or not joinable');\n}","typeGuard":"function isJoinableAppCall(call: VideoConference | null): call is VideoConference {\n  return Boolean(call && !call.endedAt && videoConfTypes.isCallManagedByApp(call));\n}","tryCatchPattern":"try {\n  const url = await VideoConfService.join(uid, callId, options);\n} catch (err) {\n  if (err instanceof Error && err.message === 'invalid-call') {\n    // racing with decline/cancel is normal: close the ringing UI, do not retry blindly\n    return;\n  }\n  throw err;\n}","preventionTips":["Treat join as racy — always handle invalid-call gracefully","Expire ringing UIs on a timer matching the server ring timeout","Listen for callEnded events to invalidate stored call ids"],"tags":["video-conference","call-state","lifecycle"],"backgroundTag":"call-already-ended","analyzedSha":"e4b8178b205510181a96ceefee043d0abcd13e5a","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}