{"record":{"id":"2bf370e90276d6a5","repo":"koala73/worldmonitor","slug":"revoke-service-is-temporarily-unavailable-try-aga","errorCode":null,"errorMessage":"Revoke service is temporarily unavailable. Try again in a moment.","messagePattern":"Revoke service is temporarily unavailable\\. Try again in a moment\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/services/mcp-clients.ts","lineNumber":100,"sourceCode":"      '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 */\nexport async function fetchMcpQuota(): Promise<McpQuota> {\n  const fallback: McpQuota = { used: 0, limit: 50, resetsAt: nextUtcMidnightIso() };\n\n  const token = await getClerkToken();\n  if (!token) return fallback;\n\n  try {\n    const resp = await fetch('/api/user/mcp-quota', {\n      method: 'GET',","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/src/services/mcp-clients.ts#L82-L118","documentation":"Mapped by revokeMcpClient() in src/services/mcp-clients.ts from an HTTP 503 returned by POST /api/user/mcp-revoke. The edge handler's callConvexRevoke() reports 'network' — and the handler answers 503 with Retry-After: 5 — when CONVEX_SITE_URL or CONVEX_SERVER_SHARED_SECRET is unset in the edge environment, when the server-to-server fetch to Convex's internal-revoke action aborts at the 3 s timeout, on transport errors, or when Convex itself returns 5xx. Crucially the revoke did NOT land, so a retry is safe.","triggerScenarios":"Edge deployment missing CONVEX_SITE_URL/CONVEX_SERVER_SHARED_SECRET env vars; Convex slow or degraded so the 3 s AbortSignal.timeout fires; transient network failure between the edge runtime and Convex; a Convex outage returning 5xx.","commonSituations":"New environments (preview/staging) where the internal Convex env vars were never added; Convex incidents; cold-start latency spikes on the edge; DNS/connectivity blips between Cloudflare edge and Convex.","solutions":["Retry after the advertised Retry-After (~5 s) — this status explicitly means the revoke did not execute.","If it fails consistently, check the edge deployment defines CONVEX_SITE_URL and CONVEX_SERVER_SHARED_SECRET.","Check the Convex status page / dashboard for deployment health during the window.","Correlate with '[mcp-revoke] Convex fetch failed:' console logs from the edge to see the underlying transport error."],"exampleFix":"// before\nawait revokeMcpClient(tokenId);\n\n// after\nasync function revokeWithRetry(tokenId: string) {\n  for (let attempt = 0; attempt < 3; attempt++) {\n    try {\n      return await revokeMcpClient(tokenId);\n    } catch (err) {\n      if (err instanceof Error && err.message.includes('temporarily unavailable') && attempt < 2) {\n        await new Promise((r) => setTimeout(r, 5000));\n        continue;\n      }\n      throw err;\n    }\n  }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isTransientRevokeFailure(err: unknown): boolean {\n  return err instanceof Error && err.message === 'Revoke service is temporarily unavailable. Try again in a moment.';\n}","tryCatchPattern":"for (let attempt = 0; ; attempt++) {\n  try {\n    return await revokeMcpClient(tokenId);\n  } catch (err) {\n    if (isTransientRevokeFailure(err) && attempt < 2) {\n      await delay(5_000); // honor Retry-After: the revoke did NOT land\n      continue;\n    }\n    throw err;\n  }\n}","preventionTips":["Honor the endpoint's Retry-After (~5 s) on 503; the operation is safe to retry because it never executed.","Ensure edge deployments define CONVEX_SITE_URL and CONVEX_SERVER_SHARED_SECRET before shipping the revoke path.","Alert on '[mcp-revoke] Convex fetch failed' edge logs to catch chronic Convex connectivity issues."],"tags":["http-503","mcp","convex","timeout","retryable","edge-functions"],"backgroundTag":"service-unavailable-503","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}