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

  1. Create and activate a new key first, then revoke the old one (rotate, don't revoke-last).
  2. Show users which key is their last active one and disable its revoke action in the UI.
  3. Serialize revocations per user (lock/queue) to avoid racing below the minimum.
  4. 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

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.