{"record":{"id":"59076c346931c2e5","repo":"RocketChat/Rocket.Chat","slug":"error-e2e-key-reset-in-progress","errorCode":null,"errorMessage":"error-e2e-key-reset-in-progress","messagePattern":"error-e2e-key-reset-in-progress","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/meteor/server/api/v1/e2e.ts","lineNumber":445,"sourceCode":"\t\t\tauthRequired: true,\n\t\t\tbody: isE2EResetRoomKeyProps,\n\t\t\tresponse: {\n\t\t\t\t400: validateBadRequestErrorResponse,\n\t\t\t\t401: validateUnauthorizedErrorResponse,\n\t\t\t\t403: validateForbiddenErrorResponse,\n\t\t\t\t200: ajv.compile<void>({\n\t\t\t\t\ttype: 'object',\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\n\t\tasync function action() {\n\t\t\tconst { rid, e2eKey, e2eKeyId } = this.bodyParams;\n\t\t\tif (!(await hasPermissionAsync(this.user, 'toggle-room-e2e-encryption', rid))) {\n\t\t\t\treturn API.v1.forbidden('error-not-allowed');\n\t\t\t}\n\t\t\tif (LockMap.has(rid)) {\n\t\t\t\tthrow new Error('error-e2e-key-reset-in-progress');\n\t\t\t}\n\n\t\t\tLockMap.set(rid, true);\n\n\t\t\tif (!(await canAccessRoomIdAsync(rid, this.userId))) {\n\t\t\t\tthrow new Error('error-not-allowed');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tawait resetRoomKey(rid, this.userId, e2eKey, e2eKeyId);\n\t\t\t\treturn API.v1.success();\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error(e);\n\t\t\t\treturn API.v1.failure('error-e2e-key-reset-failed');\n\t\t\t} finally {\n\t\t\t\tLockMap.delete(rid);\n\t\t\t}\n\t\t},","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/e2e.ts#L427-L463","documentation":"Thrown by POST e2e.resetRoomKey when a key reset is already running for the same room: the endpoint guards concurrent resets with an in-module Map (LockMap) keyed by rid and throws new Error('error-e2e-key-reset-in-progress') if LockMap.has(rid). It is a per-process, in-memory lock, so it applies to concurrent calls on the same server instance and is released in a finally block when the reset finishes or fails.","triggerScenarios":"Two POST /api/v1/e2e.resetRoomKey requests for the same rid overlapping in time — e.g. a client retrying a slow reset, double-click on 'Reset key', or two sessions of the same user resetting simultaneously.","commonSituations":"Impatient users re-clicking during a long reset on a big room; frontend retry logic with a too-short timeout; parallel key-rotation scripts hitting the same room. Note a stuck lock (see the not-allowed path that throws after LockMap.set without cleanup) keeps producing this error until the server restarts.","solutions":["Wait for the in-flight reset to finish (they are bounded by room size) and re-issue the request once.","Debounce the reset button / serialize resets per room in the client so only one request is in flight.","If the error persists long after any plausible reset duration, restart the Meteor server to clear a wedged LockMap entry."],"exampleFix":"// before\nawait api.post('e2e.resetRoomKey', body); // user can double-fire\n\n// after — one reset per room at a time, retry on in-progress\nconst inFlight = new Set();\nasync function resetRoomKey(rid, body) {\n  if (inFlight.has(rid)) return;\n  inFlight.add(rid);\n  try { await api.post('e2e.resetRoomKey', body); }\n  finally { inFlight.delete(rid); }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (let attempt = 1; attempt <= 5; attempt++) {\n  try { await api.post('e2e.resetRoomKey', body); break; }\n  catch (e) {\n    if (e.message !== 'error-e2e-key-reset-in-progress' || attempt === 5) throw e;\n    await delay(attempt * 1000); // prior reset still running for this rid\n  }\n}","preventionTips":["Serialize resetRoomKey calls per room in the client (one in flight).","Never bind the reset action to a control users can double-fire.","If it never clears, suspect a wedged server-side lock and restart the app."],"tags":["rocketchat","rest-api","e2e-encryption","concurrency","lock","retryable"],"backgroundTag":"operation-lock-contention","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}