{"record":{"id":"f6dddeaabf643cfa","repo":"TencentCloud/TencentDB-Agent-Memory","slug":"last-key-cannot-revoke","errorCode":"last_key_cannot_revoke","errorMessage":"cannot revoke the last active user key","messagePattern":"cannot revoke the last active user key","errorType":"error_code","errorClass":"MetadataError","httpStatus":null,"severity":"error","filePath":"MemoryCore/src/metadata/service/metadata-service.ts","lineNumber":748,"sourceCode":"    if (!isAdmin && !isSystemAdmin && entity.user_id !== callerUserId) {\n      throw new MetadataError(\"permission_denied\", \"cannot access another user's key\");\n    }\n    if (isSystemAdminUser(owner) && !isAdmin && callerUserId !== owner.user_id) {\n      throw new MetadataError(\"user_key_not_found\", `user key not found: ${keyId}`);\n    }\n    return this.toPublicUserKey(entity);\n  }\n\n  async revokeUserKey(keyId: string): Promise<void> {\n    const entity = await this.store.getUserKeyById(keyId);\n    if (!entity) throw new MetadataError(\"user_key_not_found\", `user key not found: ${keyId}`);\n    if (!(await this.getUserById(entity.user_id))) {\n      throw new MetadataError(\"user_key_not_found\", `user key not found: ${keyId}`);\n    }\n\n    const active = await this.store.countActiveUserKeys(entity.user_id);\n    if (active <= 1) {\n      throw new MetadataError(\"last_key_cannot_revoke\", \"cannot revoke the last active user key\");\n    }\n\n    console.info(\n      `[META] revokeUserKey: user_id=${entity.user_id} key_id=${entity.key_id} key_prefix=${maskUserKey(entity.key_value)}`,\n    );\n    await this.store.revokeUserKey(keyId, { promoteNextDefault: true });\n  }\n\n  async updateUserKey(\n    keyId: string,\n    patch: { name?: string | null; expires_at?: string | null },\n  ): Promise<UserKeyPublic> {\n    const existing = await this.store.getUserKeyById(keyId);\n    if (!existing) throw new MetadataError(\"user_key_not_found\", `user key not found: ${keyId}`);\n    if (!(await this.getUserById(existing.user_id))) {\n      throw new MetadataError(\"user_key_not_found\", `user key not found: ${keyId}`);\n    }\n","sourceCodeStart":730,"sourceCodeEnd":766,"githubUrl":"https://github.com/TencentCloud/TencentDB-Agent-Memory/blob/3efcd317b84146d6a08518ac0f7ee7c8a8d200ec/MemoryCore/src/metadata/service/metadata-service.ts#L730-L766","documentation":"MetadataError code 'last_key_cannot_revoke' thrown when revokeUserKey is called but countActiveUserKeys(owner) <= 1 — the target is (one of) the user's only active key(s). The library guarantees every user keeps at least one active key to avoid lockout.","triggerScenarios":"Revoking a user's sole remaining active key; revoking one of two keys concurrently so only one remains at check time and it is the target; active-key count miscounted due to stale store state.","commonSituations":"Users trying to rotate their only key (revoke-then-create instead of create-then-revoke); bulk cleanup scripts that don't respect the minimum; race between two revocation requests.","solutions":["Create and activate a new key first, then revoke the old one (rotate, don't revoke-last).","Show users which key is their last active one and disable its revoke action in the UI.","Serialize revocations per user (lock/queue) to avoid racing below the minimum.","If the count is wrong, audit store.countActiveUserKeys and key status values for stale data."],"exampleFix":"// before\nawait metadata.revokeUserKey(onlyKeyId); // throws last_key_cannot_revoke\n// after\nconst newKey = await metadata.createUserKey(userId, 'rotated');\nawait metadata.revokeUserKey(oldKeyId); // now at least 2 active keys exist","handlingStrategy":"validation","validationCode":"const active = (await metadata.listUserKeys(userId)).filter(k => !k.revoked_at);\nif (active.length <= 1 && active.some(k => k.key_id === keyId)) {\n  throw new Error('cannot revoke the user\\'s last active key; create a new key first');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await metadata.revokeUserKey(keyId);\n} catch (e) {\n  if (e.code === 'last_key_cannot_revoke') {\n    // guide user to rotate instead\n    const newKey = await metadata.createUserKey(userId, 'replacement');\n    await metadata.revokeUserKey(keyId);\n    return newKey;\n  }\n  throw e;\n}","preventionTips":["Always rotate (create-then-revoke), never revoke the sole key","Disable the revoke button in UI when only one active key remains","Serialize revocations per user to avoid races","Show active-key count before destructive actions"],"tags":["user-keys","revocation","business-rule","lockout-prevention"],"backgroundTag":"last-active-key-cannot-be-revoked","analyzedSha":"3efcd317b84146d6a08518ac0f7ee7c8a8d200ec","analyzedAt":"2026-09-01T05:44:22.276Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}