{"record":{"id":"fff1306f9a0ddd71","repo":"RocketChat/Rocket.Chat","slug":"invalid-call-state","errorCode":null,"errorMessage":"invalid-call-state","messagePattern":"invalid-call-state","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/services/media-call/service.ts","lineNumber":80,"sourceCode":"\t\t\tthrow new Error('not-found');\n\t\t}\n\n\t\tconst signal: ClientMediaSignalAnswer = {\n\t\t\ttype: 'answer',\n\t\t\t...params,\n\t\t};\n\n\t\tawait callServer.receiveSignal(uid, signal, { throwIfSkipped: true });\n\n\t\tconst updatedCall = await MediaCalls.findOneById(callId);\n\t\tif (!updatedCall) {\n\t\t\tthrow new Error('internal-error');\n\t\t}\n\n\t\tswitch (answer) {\n\t\t\tcase 'ack':\n\t\t\t\tif (updatedCall.acceptedAt || updatedCall.ended) {\n\t\t\t\t\tthrow new Error('invalid-call-state');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'reject':\n\t\t\t\tif (!updatedCall.ended || updatedCall.endedBy?.id !== uid) {\n\t\t\t\t\tthrow new Error('invalid-call-state');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'accept':\n\t\t\t\tif (updatedCall.callee.contractId !== signal.contractId) {\n\t\t\t\t\tif (updatedCall.callee.contractId) {\n\t\t\t\t\t\tthrow new Error('invalid-call-state');\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error('internal-error');\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn updatedCall;","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/services/media-call/service.ts#L62-L98","documentation":"In answerCall(), answer='ack' is the callee's acknowledgement that the call offer was delivered, and it is only valid while the call is still pending: neither acceptedAt nor ended may be set on the call. invalid-call-state here means the call was already accepted or already terminated when the ack arrived.","triggerScenarios":"Sending 'ack' after an 'accept' or 'reject' was already processed for the same call; an ack arriving after the caller cancelled; a duplicate ack whose second copy lands after the call state moved on.","commonSituations":"Unstable networks reordering or duplicating signals; client signaling ack and accept in quick succession; retries of 'lost' acks after the call progressed; parallel call sessions in two tabs.","solutions":["Send each call transition exactly once and only in order (offer → ack → accept/reject) from a single owning session","On invalid-call-state for an ack, resync the UI from the server's current call state instead of retrying the ack","Guard duplicate sends with an 'already acknowledged' flag in the client","Close or serialize call sessions across tabs so only one answers"],"exampleFix":"// before\nsocket.on('offer', () => {\n  sendAnswer({ callId, answer: 'ack' });\n  // ...a second handler fires later:\n  sendAnswer({ callId, answer: 'ack' }); // duplicate -> invalid-call-state\n});\n\n// after\nlet acked = false;\nsocket.on('offer', () => {\n  if (acked) return; // exactly-once\n  acked = true;\n  sendAnswer({ callId, answer: 'ack' });\n});","handlingStrategy":"try-catch","validationCode":"const call = await MediaCalls.findOneById(callId);\nif (call && !call.acceptedAt && !call.ended) {\n  await mediaCallService.answerCall(uid, { callId, answer: 'ack', /* ... */ });\n}","typeGuard":"const isCallPending = (call: IMediaCall | null): call is IMediaCall =>\n  !!call && !call.acceptedAt && !call.ended;","tryCatchPattern":"try {\n  await mediaCallService.answerCall(uid, { callId, answer: 'ack', /* ... */ });\n} catch (err) {\n  if (err instanceof Error && err.message === 'invalid-call-state') {\n    // ack is pointless now: resync from the server's call state, do not resend\n    return resyncCallState(callId);\n  }\n  throw err;\n}","preventionTips":["Send each call signal exactly once from a single owning session","On invalid-call-state, resync from server state rather than retrying","Ignore offers for calls the local state already marks accepted/ended"],"tags":["webrtc","media-call","state-machine","call-signaling","rocket-chat"],"backgroundTag":"invalid-state-transition","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}