{"record":{"id":"f3347d25673acbe6","repo":"koala73/worldmonitor","slug":"this-client-was-already-revoked-or-no-longer-exist","errorCode":null,"errorMessage":"This client was already revoked or no longer exists.","messagePattern":"This client was already revoked or no longer exists\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/services/mcp-clients.ts","lineNumber":91,"sourceCode":" * Throws on non-2xx so the UI can surface the error.\n */\nexport async function revokeMcpClient(tokenId: string): Promise<void> {\n  const token = await getClerkToken();\n  if (!token) throw new Error('Sign in to revoke MCP clients.');\n\n  const resp = await fetch('/api/user/mcp-revoke', {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n      Authorization: `Bearer ${token}`,\n    },\n    body: JSON.stringify({ tokenId }),\n  });\n\n  if (resp.ok) return;\n\n  if (resp.status === 404) {\n    throw new Error('This client was already revoked or no longer exists.');\n  }\n  if (resp.status === 409) {\n    throw new Error('This client was already revoked.');\n  }\n  if (resp.status === 401) {\n    throw new Error('Sign in to revoke MCP clients.');\n  }\n  if (resp.status === 503) {\n    throw new Error('Revoke service is temporarily unavailable. Try again in a moment.');\n  }\n  throw new Error(`Revoke failed (HTTP ${resp.status}).`);\n}\n\n/**\n * Fetch the caller's daily Pro MCP quota usage. Returns sane defaults on\n * any failure — the settings UI is informational and should never break\n * because the quota counter is unreachable.\n */","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/src/services/mcp-clients.ts#L73-L109","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Refresh the client list (listMcpClients()) and reconcile the UI — drop rows that no longer exist.","Confirm the tokenId comes from the same deployment's list call (no dev/prod mixing via VITE_CONVEX_URL).","Treat this outcome as success-equivalent for UX: the client cannot be used, so remove it from the display.","Do not blindly retry — 404 is authoritative and idempotent."],"exampleFix":"// before\nawait revokeMcpClient(tokenId);\n\n// after\ntry {\n  await revokeMcpClient(tokenId);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('already revoked or no longer exists')) {\n    removeClientFromUi(tokenId);\n    return;\n  }\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":"const clients = await listMcpClients();\nconst existsAndMine = clients.some((c) => c.id === tokenId && !c.revokedAt);\nif (!existsAndMine) {\n  removeClientFromUi(tokenId); // stale row — nothing to revoke\n  return;\n}\nawait revokeMcpClient(tokenId);","typeGuard":"function isNotFoundRevoke(err: unknown): boolean {\n  return err instanceof Error && err.message === 'This client was already revoked or no longer exists.';\n}","tryCatchPattern":"try {\n  await revokeMcpClient(tokenId);\n} catch (err) {\n  if (isNotFoundRevoke(err)) {\n    removeClientFromUi(tokenId); // terminal, idempotent — do not retry\n    return;\n  }\n  throw err;\n}","preventionTips":["Refresh the client list before revoking to avoid stale tokenIds.","Never mix deployments: list and revoke must hit the same Convex backend.","Remember 404 also covers 'not your row' (anti-enumeration) — do not probe foreign ids."],"tags":["http-404","mcp","convex","not-found","anti-enumeration"],"backgroundTag":"resource-not-found-404","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}