{"record":{"id":"859f31dc27420850","repo":"paperclipai/paperclip","slug":"github-inventory-failed-installation-token-was-mi","errorCode":null,"errorMessage":"GitHub inventory failed: installation token was missing","messagePattern":"GitHub inventory failed: installation token was missing","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/chat-provider-inventory.ts","lineNumber":189,"sourceCode":"  appJwt: string;\n  installationId: string;\n  fetch: typeof globalThis.fetch;\n}): Promise<ChatProviderInventoryResult> {\n  const headers = {\n    accept: \"application/vnd.github+json\",\n    authorization: `Bearer ${input.appJwt}`,\n    \"x-github-api-version\": \"2022-11-28\",\n  };\n  const tokenResponse = await input.fetch(\n    `https://api.github.com/app/installations/${encodeURIComponent(input.installationId)}/access_tokens`,\n    { method: \"POST\", headers, signal: githubRequestSignal() },\n  );\n  const tokenBody = await jsonResponse<{ token?: string; message?: string }>(\n    tokenResponse,\n    \"GitHub\",\n  );\n  if (!tokenBody.token)\n    throw new Error(\"GitHub inventory failed: installation token was missing\");\n\n  const resources: ChatProviderResourceInventoryItem[] = [];\n  let page = 1;\n  try {\n    while (true) {\n      const url = new URL(\"https://api.github.com/installation/repositories\");\n      url.searchParams.set(\"per_page\", \"100\");\n      url.searchParams.set(\"page\", String(page));\n      const response = await input.fetch(url, {\n        headers: {\n          accept: \"application/vnd.github+json\",\n          authorization: `Bearer ${tokenBody.token}`,\n          \"x-github-api-version\": \"2022-11-28\",\n        },\n        signal: githubRequestSignal(),\n      });\n      const body = await jsonResponse<{\n        repositories?: Array<{","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-provider-inventory.ts#L171-L207","documentation":"Thrown by listGitHubInstallationRepositories when the GitHub App installation-token exchange succeeded at HTTP level but the response body contains no 'token' field. The installation token is required for all subsequent /installation/repositories page fetches, so without it the inventory cannot proceed.","triggerScenarios":"The token-access response (POST /app/installations/{id}/access_tokens) parses OK but tokenBody.token is undefined — e.g. GitHub returned an unexpected shape, an error envelope with only 'message', or a proxied/mutated response.","commonSituations":"GitHub API partial outage returning an error envelope with 200; proxy rewriting the response; App suspended between auth steps; response contract drift from an API gateway in front of the server.","solutions":["Retry the repository inventory; a transient malformed token response usually resolves on retry.","Reconnect the GitHub App connection to restart the auth flow with fresh App credentials.","Confirm the App is not suspended on GitHub (suspended apps cannot mint installation tokens).","Bypass or inspect any proxy between the server and api.github.com for body rewriting."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before inventory, confirm the App can mint tokens (not suspended):\nconst inst = await gh.request('GET /app/installations');\nif (!inst.data.some(i => !i.suspended_at)) {\n  throw new Error('No active installation; install/reactivate the App first');\n}","typeGuard":"function hasInstallationToken(b: { token?: string; message?: string }): b is { token: string; message?: string } {\n  return typeof b.token === 'string' && b.token.length > 0;\n}","tryCatchPattern":"try { const repos = await listGitHubInstallationRepositories(input); }\ncatch (e) {\n  if (e.message.includes('installation token was missing')) {\n    await retryWithBackoff(() => listGitHubInstallationRepositories(input), 2);\n    // if persistent, force reconnect to restart token exchange\n  }\n}","preventionTips":["Ensure the App is active and unsuspended before inventory runs.","Retry transient malformed token responses; escalate to reconnect if repeated.","Avoid proxies that mutate GitHub API response bodies.","Monitor GitHub API incidents affecting token minting."],"tags":["github","auth","installation-token","inventory"],"backgroundTag":"missing-credentials","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}