{"record":{"id":"78d67442323f0d1e","repo":"paperclipai/paperclip","slug":"github-inventory-failed-the-active-installation-h","errorCode":null,"errorMessage":"GitHub inventory failed: the active installation has not granted the required access for: ${missingPermissions.join(\", \")}. Approve the GitHub App permission update, then retry","messagePattern":"GitHub inventory failed: the active installation has not granted the required access for: (.+?)\\. Approve the GitHub App permission update, then retry","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/chat-provider-inventory.ts","lineNumber":317,"sourceCode":"      \"GitHub inventory failed: install this GitHub App on the selected repositories first\",\n    );\n  }\n  if (active.length !== 1) {\n    throw new Error(\n      \"GitHub inventory failed: this chat connection requires a dedicated GitHub App with exactly one active installation\",\n    );\n  }\n  const installation = active[0]!;\n  const missingPermissions = Object.entries(\n    REQUIRED_GITHUB_INSTALLATION_PERMISSIONS,\n  )\n    .filter(\n      ([permission, access]) =>\n        installation.permissions?.[permission] !== access,\n    )\n    .map(([permission]) => permission);\n  if (missingPermissions.length > 0) {\n    throw new Error(\n      `GitHub inventory failed: the active installation has not granted the required access for: ${missingPermissions.join(\", \")}. Approve the GitHub App permission update, then retry`,\n    );\n  }\n  return {\n    installationId: String(installation.id),\n    accountId: Number.isFinite(installation.account?.id)\n      ? String(installation.account?.id)\n      : undefined,\n    accountLabel:\n      installation.account?.login ?? installation.account?.name ?? undefined,\n    accountType: installation.account?.type,\n    permissions: installation.permissions ?? {},\n  };\n}\n","sourceCodeStart":299,"sourceCodeEnd":332,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-provider-inventory.ts#L299-L332","documentation":"After finding the single active installation, discoverDedicatedGitHubAppInstallation compares installation.permissions against REQUIRED_GITHUB_INSTALLATION_PERMISSIONS. Any permission whose granted access differs from the required value is collected; if the list is non-empty the error names the missing permissions and asks the installer to approve the pending permission update in GitHub.","triggerScenarios":"Running GitHub chat inventory discovery when the app's saved installation permissions (installation.permissions[key]) do not equal the required access for one or more keys in REQUIRED_GITHUB_INSTALLATION_PERMISSIONS (e.g. after the app manifest added new permissions the owner has not accepted).","commonSituations":"Upgrading the GitHub App with new/changed permissions while the installation owner never approved the 'permission update' request email; installing an older version of the app whose permissions predate what inventory needs.","solutions":["Open the GitHub App settings page and approve the pending permission-change request for the installation, then retry.","Have the installation owner visit https://github.com/settings/installations and accept the permission update for the app.","If no pending request exists, bump the app manifest permissions to match REQUIRED_GITHUB_INSTALLATION_PERMISSIONS and re-request them from the installation owner.","Reinstall the app on the target repositories with the current permission set granted."],"exampleFix":"// before: app requested contents:read but installation granted contents: none\n// missingPermissions = ['contents'] -> throws\n// after: owner approves permission update so installation.permissions.contents === 'read'\n// -> discovery returns installationId/accountId","handlingStrategy":"validation","validationCode":"const inst = await getAppInstallation(octokit, installationId);\nconst missing = Object.entries(REQUIRED_GITHUB_INSTALLATION_PERMISSIONS)\n  .filter(([p, access]) => inst.permissions?.[p] !== access)\n  .map(([p]) => p);\nif (missing.length) throw new Error(`Approve permission update for: ${missing.join(\", \")}`);","typeGuard":"function hasRequiredPermissions(inst: { permissions?: Record<string, string> }): boolean {\n  return Object.entries(REQUIRED_GITHUB_INSTALLATION_PERMISSIONS)\n    .every(([p, a]) => inst.permissions?.[p] === a);\n}","tryCatchPattern":"try {\n  await discoverDedicatedGitHubAppInstallation(conn);\n} catch (e) {\n  if (e.message.includes(\"required access\")) {\n    // prompt user to approve the GitHub App permission update, then retry\n  }\n}","preventionTips":["After changing app manifest permissions, always notify the installation owner to approve the pending update before re-running inventory.","Pre-flight check installation.permissions against REQUIRED_GITHUB_INSTALLATION_PERMISSIONS on every run.","Subscribe to GitHub App permission-change webhooks/emails and act on them.","Keep the app manifest permissions minimal but identical to what the code requires."],"tags":["github","permissions","oauth-app"],"backgroundTag":"insufficient-permissions","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"}