{"record":{"id":"61e0282b087da7ee","repo":"RocketChat/Rocket.Chat","slug":"error-agent-is-locked","errorCode":"error-agent-is-locked","errorMessage":"error-agent-is-locked","messagePattern":"error-agent-is-locked","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/omnichannel/RoutingManager.ts","lineNumber":274,"sourceCode":"\t\tif (!room?.open) {\n\t\t\tlogger.debug({ msg: 'Cannot take inquiry. Room is closed', inquiryId: inquiry._id });\n\t\t\treturn room;\n\t\t}\n\n\t\tif (room.servedBy && room.servedBy._id === agent.agentId) {\n\t\t\tlogger.debug({ msg: 'Cannot take inquiry. Already taken by agent', inquiryId: inquiry._id, agentId: room.servedBy._id });\n\t\t\treturn room;\n\t\t}\n\n\t\tconst lock = await conditionalLockAgent(agent.agentId);\n\t\tif (!lock.acquired && lock.required) {\n\t\t\tlogger.debug({\n\t\t\t\tmsg: 'Cannot take inquiry because agent is currently locked by another process',\n\t\t\t\tagentId: agent.agentId,\n\t\t\t\tinquiryId: _id,\n\t\t\t});\n\t\t\tif (options.clientAction && !options.forwardingToDepartment) {\n\t\t\t\tthrow new Error('error-agent-is-locked');\n\t\t\t}\n\t\t\tagent = null;\n\t\t}\n\n\t\tif (agent) {\n\t\t\ttry {\n\t\t\t\tawait callbacks.run('livechat.checkAgentBeforeTakeInquiry', {\n\t\t\t\t\tagent,\n\t\t\t\t\tinquiry,\n\t\t\t\t\toptions,\n\t\t\t\t});\n\t\t\t} catch (e) {\n\t\t\t\tawait lock.unlock();\n\t\t\t\tif (options.clientAction && !options.forwardingToDepartment) {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t\tagent = null;\n\t\t\t}","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/lib/omnichannel/RoutingManager.ts#L256-L292","documentation":"RoutingManager.takeInquiry acquires a per-agent conditional lock (conditionalLockAgent) before assigning an inquiry to an agent. If the lock cannot be acquired because another process currently holds it, and the operation is a client-initiated take that is not a department forward, a plain Error('error-agent-is-locked') is thrown; otherwise the agent is dropped and routing falls back. This signals short-lived concurrency contention, not a data problem.","triggerScenarios":"Two concurrent client take actions targeting the same agent - agent double-clicks Take, two browser tabs, or multiple Meteor instances routing simultaneously - while one of them still holds the agent lock; or a crashed process left the lock held beyond its expected lifetime.","commonSituations":"Horizontally scaled deployments where several app instances process the same inquiry queue; slow MongoDB making lock windows overlap; UIs that allow rapid repeated take clicks.","solutions":["Retry the take action after a short backoff - the contention window is typically sub-second","Debounce or disable the Take control until the in-flight request completes, preventing duplicate concurrent takes","In multi-instance deployments, verify agent-lock TTL and ownership settings so locks are released predictably","If the error persists, inspect the agent lock document and clear the stale lock only after confirming no live holder"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"const takeWithRetry = async (inquiryId: string, options: TakeOptions, attempts = 3) => {\n  try {\n    return await RoutingManager.takeInquiry(inquiryId, options);\n  } catch (err: any) {\n    if (err?.message === 'error-agent-is-locked' && attempts > 0) {\n      await sleep(200 * (4 - attempts)); // contention is short-lived; brief backoff\n      return takeWithRetry(inquiryId, options, attempts - 1);\n    }\n    throw err;\n  }\n};","preventionTips":["Debounce the 'Take' action in the UI so rapid clicks cannot race each other","Ensure only one routing path (manual or automatic) can act on an inquiry concurrently","In multi-instance deployments, monitor lock hold time and tune the agent-lock TTL so crashed holders release quickly"],"tags":["omnichannel","routing","concurrency","locking","transient"],"backgroundTag":"lock-contention","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}