{"record":{"id":"cb06569c5c2c0e56","repo":"paperclipai/paperclip","slug":"provider-inventory-failed-message","errorCode":null,"errorMessage":"${provider} inventory failed: ${message}","messagePattern":"(.+?) inventory failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/chat-provider-inventory.ts","lineNumber":56,"sourceCode":"  return AbortSignal.timeout(GITHUB_API_TIMEOUT_MS);\n}\n\nasync function jsonResponse<T>(\n  response: Response,\n  provider: string,\n): Promise<T> {\n  let body: unknown;\n  try {\n    body = await response.json();\n  } catch {\n    throw new Error(`${provider} returned an unreadable inventory response`);\n  }\n  if (!response.ok) {\n    const message =\n      body && typeof body === \"object\" && \"message\" in body\n        ? String((body as { message?: unknown }).message)\n        : String(response.status);\n    throw new Error(`${provider} inventory failed: ${message}`);\n  }\n  return body as T;\n}\n\n/** List only Slack conversations where the installed bot is a member. */\nexport async function listSlackBotChannels(input: {\n  botToken: string;\n  fetch: typeof globalThis.fetch;\n}): Promise<ChatProviderInventoryResult> {\n  const resources: ChatProviderResourceInventoryItem[] = [];\n  let cursor = \"\";\n  do {\n    const url = new URL(\"https://slack.com/api/conversations.list\");\n    url.searchParams.set(\"types\", \"public_channel,private_channel\");\n    url.searchParams.set(\"exclude_archived\", \"true\");\n    url.searchParams.set(\"limit\", \"200\");\n    if (cursor) url.searchParams.set(\"cursor\", cursor);\n    const response = await input.fetch(url, {","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-provider-inventory.ts#L38-L74","documentation":"Thrown by jsonResponse when the provider responds with a non-OK HTTP status (response.ok is false). The helper extracts the provider's 'message' field from the JSON body when present, otherwise falls back to the numeric status, and wraps it as '<provider> inventory failed: <message>'. This is the generic upstream-API-error path for GitHub/Slack inventory calls.","triggerScenarios":"Any 4xx/5xx from the provider during inventory: 401 invalid/expired token, 403 missing scopes or suspended app, 404 wrong resource/endpoint, 429 rate limited, 5xx provider outage.","commonSituations":"Expired or revoked Slack bot token / GitHub installation token; insufficient scopes after reinstall; hitting Slack/GitHub rate limits during large inventory scans; provider incident.","solutions":["Read the embedded message: fix the specific cause (refresh token, add scopes, back off).","If 401/403, reconnect the chat connection to obtain fresh tokens/scopes.","If 429, implement/respect exponential backoff using Retry-After headers.","Check provider status pages and retry on 5xx.","Verify the account/app has not been deactivated or suspended."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { const inv = await listProviderResources(input); }\ncatch (e) {\n  const msg = e.message; // '<Provider> inventory failed: <detail>'\n  if (msg.includes('429') || /rate/i.test(msg)) await backoffAndRetry();\n  else if (/401|403|token|scope/i.test(msg)) await promptReconnect();\n  else throw e;\n}","preventionTips":["Refresh provider tokens before expiry (Slack bot tokens, GitHub installation tokens).","Request all required scopes at install time to avoid 403s.","Throttle inventory scans and honor Retry-After on 429.","Alert on provider 5xx and retry with exponential backoff."],"tags":["http-error","api","inventory","github","slack"],"backgroundTag":"upstream-api-error","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}