RocketChat/Rocket.Chat · error
error-taking-inquiry-lockedAt-mismatch
error-taking-inquiry-lockedAt-mismatch
Error message
error-taking-inquiry-lockedAt-mismatch
What it means
Livechat inquiries are taken with optimistic concurrency: LivechatInquiry.takeInquiry(_id, lockedAt) only updates the document when lockedAt still matches the value that was read earlier. When modifiedCount is 0, the inquiry was concurrently locked, taken, or rewritten by someone else between the read and the write, and RoutingManager throws Error('error-taking-inquiry-lockedAt-mismatch') instead of double-assigning the chat.
Source
Thrown at apps/meteor/server/lib/omnichannel/RoutingManager.ts:308
}
agent = null;
}
}
if (!agent) {
logger.debug({ msg: 'Cannot take inquiry. Precondition failed for agent', inquiryId: inquiry._id });
const cbRoom = await callbacks.run<'livechat.onAgentAssignmentFailed'>('livechat.onAgentAssignmentFailed', room, {
inquiry,
options,
});
return cbRoom;
}
try {
const result = await LivechatInquiry.takeInquiry(_id, inquiry.lockedAt);
if (result.modifiedCount === 0) {
logger.error({ msg: 'Failed to take inquiry because lockedAt did not match', inquiryId: _id, lockedAt: inquiry.lockedAt });
throw new Error('error-taking-inquiry-lockedAt-mismatch');
}
logger.info({ msg: 'Inquiry taken', inquiryId: _id, agentId: agent.agentId });
// assignAgent changes the room data to add the agent serving the conversation. afterTakeInquiry expects room object to be updated
const { inquiry: returnedInquiry, user } = await this.assignAgent(inquiry, agent);
const roomAfterUpdate = await LivechatRooms.findOneById(rid);
if (!roomAfterUpdate) {
// This should never happen
throw new Error('error-room-not-found');
}
void Apps.self?.triggerEvent(AppEvents.IPostLivechatAgentAssigned, { room: roomAfterUpdate, user });
void afterTakeInquiry({ inquiry: returnedInquiry, room: roomAfterUpdate, agent });
void notifyOnLivechatInquiryChangedById(inquiry._id, 'updated', {
status: LivechatInquiryStatus.TAKEN,View on GitHub (pinned to b2c16d5842)
Solutions
- Re-fetch the inquiry and retry the take only if it is still queued - someone else most likely won the race
- Refresh the queue view so already-taken inquiries disappear from agent lists
- Ensure only one assignment path (auto-routing OR manual take) can act on an inquiry at a time
- Surface a friendly 'chat already taken' outcome instead of treating it as a failure
Defensive patterns
Strategy: retry
Try / catch
try {
await RoutingManager.takeInquiry(_id, options);
} catch (err: any) {
if (err?.message === 'error-taking-inquiry-lockedAt-mismatch') {
const fresh = await LivechatInquiry.findOneById(_id);
if (fresh && !fresh.servedBy) {
return RoutingManager.takeInquiry(_id, options); // someone rewrote it; retry once with fresh lockedAt
}
// lost the race: refresh the queue view and move on
}
} Prevention
- Keep the inquiry queue view near-real-time so agents stop clicking already-taken chats
- Never run auto-assignment and manual take against the same queue simultaneously
- Treat this as an expected race outcome ('chat already taken'), not an incident
When it happens
Trigger: Two actors (two agents, or an agent plus auto-routing) read the same queued inquiry and both attempt to take it: the first update wins, the second sees modifiedCount 0 and gets this error. Also fired when a department forward or routing callback rewrites the inquiry between the read and the take.
Common situations: Busy queues where several agents click the same inquiry at once; auto-assignment racing with manual takes; duplicated websocket events re-triggering the take for the same inquiry.
Related errors
- error-agent-is-locked
- error-inquiry-taken
- Business hour ${department.businessHourId} not found
- This_conversation_is_already_closed
- error-no-agents-available-for-service-on-department
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/6a7de50f6b8db40a.
Report an issue: GitHub.