koala73/worldmonitor · warning · Error

This client was already revoked or no longer exists.

Error message

This client was already revoked or no longer exists.

What it means

Mapped by revokeMcpClient() in src/services/mcp-clients.ts from an HTTP 404 returned by POST /api/user/mcp-revoke. The edge handler translates Convex's NOT_FOUND into 404 and deliberately collapses three server cases for anti-enumeration: the tokenId never existed, the id is malformed, or the row exists but is owned by a different user (the Convex mutation asserts row.userId === caller). It is a deterministic terminal outcome — retrying with the same id will always 404.

Source

Thrown at src/services/mcp-clients.ts:91

 * Throws on non-2xx so the UI can surface the error.
 */
export async function revokeMcpClient(tokenId: string): Promise<void> {
  const token = await getClerkToken();
  if (!token) throw new Error('Sign in to revoke MCP clients.');

  const resp = await fetch('/api/user/mcp-revoke', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify({ tokenId }),
  });

  if (resp.ok) return;

  if (resp.status === 404) {
    throw new Error('This client was already revoked or no longer exists.');
  }
  if (resp.status === 409) {
    throw new Error('This client was already revoked.');
  }
  if (resp.status === 401) {
    throw new Error('Sign in to revoke MCP clients.');
  }
  if (resp.status === 503) {
    throw new Error('Revoke service is temporarily unavailable. Try again in a moment.');
  }
  throw new Error(`Revoke failed (HTTP ${resp.status}).`);
}

/**
 * Fetch the caller's daily Pro MCP quota usage. Returns sane defaults on
 * any failure — the settings UI is informational and should never break
 * because the quota counter is unreachable.
 */

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Refresh the client list (listMcpClients()) and reconcile the UI — drop rows that no longer exist.
  2. Confirm the tokenId comes from the same deployment's list call (no dev/prod mixing via VITE_CONVEX_URL).
  3. Treat this outcome as success-equivalent for UX: the client cannot be used, so remove it from the display.
  4. Do not blindly retry — 404 is authoritative and idempotent.

Example fix

// before
await revokeMcpClient(tokenId);

// after
try {
  await revokeMcpClient(tokenId);
} catch (err) {
  if (err instanceof Error && err.message.includes('already revoked or no longer exists')) {
    removeClientFromUi(tokenId);
    return;
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const clients = await listMcpClients();
const existsAndMine = clients.some((c) => c.id === tokenId && !c.revokedAt);
if (!existsAndMine) {
  removeClientFromUi(tokenId); // stale row — nothing to revoke
  return;
}
await revokeMcpClient(tokenId);

Type guard

function isNotFoundRevoke(err: unknown): boolean {
  return err instanceof Error && err.message === 'This client was already revoked or no longer exists.';
}

Try / catch

try {
  await revokeMcpClient(tokenId);
} catch (err) {
  if (isNotFoundRevoke(err)) {
    removeClientFromUi(tokenId); // terminal, idempotent — do not retry
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Revoking from a stale list where the token was already revoked and pruned server-side; passing a tokenId obtained from a different Convex deployment (dev data against prod); a hand-copied/truncated id; attempting to revoke a row that belongs to another account after a session switch.

Common situations: Two tabs open where one already revoked the client; environments mixed up between local dev and production Convex; token rows removed by an admin/script between listing and revoking.

Related errors


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/f3347d25673acbe6. Report an issue: GitHub.