TencentCloud/TencentDB-Agent-Memory · error · MetadataError
last_key_cannot_revoke
last_key_cannot_revoke
Error message
cannot revoke the last active user key
What it means
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.
Source
Thrown at MemoryCore/src/metadata/service/metadata-service.ts:748
if (!isAdmin && !isSystemAdmin && entity.user_id !== callerUserId) {
throw new MetadataError("permission_denied", "cannot access another user's key");
}
if (isSystemAdminUser(owner) && !isAdmin && callerUserId !== owner.user_id) {
throw new MetadataError("user_key_not_found", `user key not found: ${keyId}`);
}
return this.toPublicUserKey(entity);
}
async revokeUserKey(keyId: string): Promise<void> {
const entity = await this.store.getUserKeyById(keyId);
if (!entity) throw new MetadataError("user_key_not_found", `user key not found: ${keyId}`);
if (!(await this.getUserById(entity.user_id))) {
throw new MetadataError("user_key_not_found", `user key not found: ${keyId}`);
}
const active = await this.store.countActiveUserKeys(entity.user_id);
if (active <= 1) {
throw new MetadataError("last_key_cannot_revoke", "cannot revoke the last active user key");
}
console.info(
`[META] revokeUserKey: user_id=${entity.user_id} key_id=${entity.key_id} key_prefix=${maskUserKey(entity.key_value)}`,
);
await this.store.revokeUserKey(keyId, { promoteNextDefault: true });
}
async updateUserKey(
keyId: string,
patch: { name?: string | null; expires_at?: string | null },
): Promise<UserKeyPublic> {
const existing = await this.store.getUserKeyById(keyId);
if (!existing) throw new MetadataError("user_key_not_found", `user key not found: ${keyId}`);
if (!(await this.getUserById(existing.user_id))) {
throw new MetadataError("user_key_not_found", `user key not found: ${keyId}`);
}
View on GitHub (pinned to 3efcd317b8)
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.
Example fix
// before await metadata.revokeUserKey(onlyKeyId); // throws last_key_cannot_revoke // after const newKey = await metadata.createUserKey(userId, 'rotated'); await metadata.revokeUserKey(oldKeyId); // now at least 2 active keys exist
Defensive patterns
Strategy: validation
Validate before calling
const active = (await metadata.listUserKeys(userId)).filter(k => !k.revoked_at);
if (active.length <= 1 && active.some(k => k.key_id === keyId)) {
throw new Error('cannot revoke the user\'s last active key; create a new key first');
} Try / catch
try {
await metadata.revokeUserKey(keyId);
} catch (e) {
if (e.code === 'last_key_cannot_revoke') {
// guide user to rotate instead
const newKey = await metadata.createUserKey(userId, 'replacement');
await metadata.revokeUserKey(keyId);
return newKey;
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
AI-assisted analysis of TencentCloud/TencentDB-Agent-Memory@3efcd317b8 (2026-09-01).
Data as JSON: /api/errors/f6dddeaabf643cfa.
Report an issue: GitHub.