RocketChat/Rocket.Chat · warning · Error
invalid-call-status
Error message
invalid-call-status
What it means
Thrown by VideoConferenceService.cancel when the direct call exists but is not cancellable: status !== VideoConferenceStatus.CALLING, or endedBy/endedAt is already set. Cancel means declining while the phone is still ringing; once the call was accepted, declined elsewhere, or timed out, this transition is rejected. This guard is inherently racy in real UIs.
Source
Thrown at apps/meteor/server/services/video-conference/service.ts:228
{
blockId: 'videoconf-info',
type: 'section',
text: {
type: 'mrkdwn',
text: `**${i18n.t('Video_Conference_Url')}**: ${call.url}`,
},
},
];
}
public async cancel(uid: IUser['_id'], callId: VideoConference['_id']): Promise<void> {
const call = await VideoConferenceModel.findOneById(callId);
if (!call || !isDirectVideoConference(call)) {
throw new Error('invalid-call');
}
if (call.status !== VideoConferenceStatus.CALLING || call.endedBy || call.endedAt) {
throw new Error('invalid-call-status');
}
const user = await Users.findOneById(uid);
if (!user) {
throw new Error('failed-to-load-own-data');
}
await VideoConferenceModel.setDataById(callId, {
ringing: false,
status: VideoConferenceStatus.DECLINED,
endedAt: new Date(),
endedBy: {
_id: user._id,
name: user.name as string,
username: user.username as string,
},
});
View on GitHub (pinned to e4b8178b20)
Solutions
- Treat invalid-call-status on cancel as benign — refresh the UI to the call's current status instead of retrying
- Drive the ringing modal from live call-status events so it closes as soon as status changes
- Debounce the decline button to kill double-submits
- Optionally fetch current status before cancelling, but keep the catch because the race cannot be fully prevented
Example fix
// before
await VideoConfService.cancel(uid, callId);
// after
try {
await VideoConfService.cancel(uid, callId);
} catch (err) {
if (err instanceof Error && err.message === 'invalid-call-status') {
// already answered/declined/ended — just refresh call state
} else {
throw err;
}
} Defensive patterns
Strategy: try-catch
Validate before calling
const call = await VideoConferenceModel.findOneById(callId);
if (call?.status !== VideoConferenceStatus.CALLING || call.endedBy || call.endedAt) {
// no longer ringing: refresh state instead of cancelling
} Type guard
function isRingingCall(call: VideoConference | null): call is VideoConference {
return Boolean(call && call.status === VideoConferenceStatus.CALLING && !call.endedBy && !call.endedAt);
} Try / catch
try {
await VideoConfService.cancel(uid, callId);
} catch (err) {
if (err instanceof Error && err.message === 'invalid-call-status') return; // benign race: refresh UI
throw err;
} Prevention
- Never retry cancel on invalid-call-status — refetch the status instead
- Debounce decline buttons
- Drive ringing UI from a call-status stream, not one-shot state
When it happens
Trigger: Caller cancel and callee decline racing (the loser gets this error); cancelling after the caller hung up; ringing timeout already moved status away from CALLING; double-tap on the decline button firing two requests.
Common situations: Two devices acting on the same ringing call; UI retrying a timed-out cancel; stale ringing modal after the call was answered elsewhere.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- invalid-call
- failed-to-create-direct-call
- failed-to-create-group-call
- video-conf-data-not-found
- A video conference must exist to update.
AI-assisted analysis of RocketChat/Rocket.Chat@e4b8178b20 (2026-08-18).
Data as JSON: /api/errors/60b44049d19936ea.
Report an issue: GitHub.