{"record":{"id":"bcd9cb96352aeda5","repo":"mastra-ai/mastra","slug":"github-installation-is-invalid","errorCode":null,"errorMessage":"GitHub installation is invalid.","messagePattern":"GitHub installation is invalid\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/integrations/github/session-subscriptions.ts","lineNumber":185,"sourceCode":"export async function unsubscribeCurrentSessionFromPullRequest(\n  requestContext: RequestContext,\n  pullRequest: number | string,\n  github: GithubIntegration,\n) {\n  const target = await resolveSessionTarget(requestContext, github);\n  const number = parsePullRequest(pullRequest, target.repository.slug);\n  await unsubscribeFromPullRequest(await subscriptionInput(target, number), github.integrationStorage);\n  return number;\n}\n\nexport async function upsertFactoryTriageComment(\n  requestContext: RequestContext,\n  input: { issueNumber: number; body: string },\n  github: GithubIntegration,\n) {\n  const target = await resolveSessionTarget(requestContext, github);\n  const installationId = Number(target.installation.externalId);\n  if (!Number.isSafeInteger(installationId) || installationId <= 0) throw new Error('GitHub installation is invalid.');\n  return serializeTriageComment(`${installationId}:${target.repository.externalId}:${input.issueNumber}`, () =>\n    github.upsertFactoryTriageComment({\n      installationId,\n      repository: target.repository.slug,\n      issueNumber: input.issueNumber,\n      body: input.body,\n    }),\n  );\n}\n\nexport async function refreshGithubToken(requestContext: RequestContext, github: GithubIntegration): Promise<void> {\n  const target = await resolveSessionTarget(requestContext, github);\n  // `GH_TOKEN` feeds the `gh` CLI, so a configured org PAT wins over a minted\n  // installation token (which 403s on integration-restricted endpoints). The\n  // workspace records which PAT kind the sandbox was provisioned with, so a\n  // review-board sandbox keeps its reviewer token on refresh.\n  const pat = await getGithubPat(\n    () => github.integrationStorage,","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/integrations/github/session-subscriptions.ts#L167-L203","documentation":"upsertFactoryTriageComment resolves the GitHub App installation for the current Factory session before writing a triage comment. The installation's externalId must convert to a safe positive integer, which is the only valid GitHub installation ID format. If the stored installation row is missing an externalId, holds a non-numeric value (e.g. null, undefined, or a string), or is zero/negative, the function refuses to proceed so a malformed installation record is never used for API calls.","triggerScenarios":"Calling upsertFactoryTriageComment (directly or via the GitHub subscription tools created by createGithubSubscriptionTools) when resolveSessionTarget returns a target whose installation.externalId is undefined, null, non-numeric, 0, or negative.","commonSituations":"A GitHub App installation was created outside the normal flow so externalId was never persisted; a migration or storage backfill left the installation row incomplete; the org connected a non-GitHub provider whose externalId format differs; data corruption in the integration storage.","solutions":["Re-connect the GitHub App installation for the org so a fresh installation row with a valid numeric externalId is stored","Inspect the installation row in integration storage (installation.externalId) and fix/backfill the externalId to the numeric GitHub installation ID","Verify the session's target repository belongs to the org whose GitHub installation is actually installed (not a partial install)","Add a pre-check on Number(installation.externalId) before calling upsertFactoryTriageComment and surface a clear reconnect prompt"],"exampleFix":"// before\nawait upsertFactoryTriageComment(requestContext, { issueNumber: 12, body: 'triage' }, github);\n// after\nconst target = await resolveSessionTarget(requestContext, github);\nconst installationId = Number(target.installation.externalId);\nif (!Number.isSafeInteger(installationId) || installationId <= 0) {\n  throw new Error('GitHub App installation is missing or invalid — reconnect the installation.');\n}\nawait upsertFactoryTriageComment(requestContext, { issueNumber: 12, body: 'triage' }, github);","handlingStrategy":"validation","validationCode":"const target = await resolveSessionTarget(requestContext, github);\nconst installationId = Number(target.installation.externalId);\nif (!Number.isSafeInteger(installationId) || installationId <= 0) {\n  throw new Error('GitHub installation not connected or invalid — reconnect the GitHub App.');\n}","typeGuard":"function hasValidInstallation(t: { installation: { externalId: unknown } }): t is { installation: { externalId: string | number } } {\n  const id = Number(t.installation.externalId);\n  return Number.isSafeInteger(id) && id > 0;\n}","tryCatchPattern":"try {\n  await upsertFactoryTriageComment(requestContext, input, github);\n} catch (err) {\n  if ((err as Error).message === 'GitHub installation is invalid.') {\n    promptGithubReconnect(requestContext);\n    return;\n  }\n  throw err;\n}","preventionTips":["Re-connect GitHub App installations through the standard flow so externalId is always persisted","Validate installation.externalId right after resolving the session target","Alert on installation rows with missing/non-numeric externalId via a storage health check","Test the triage-comment path against orgs with partial or legacy installations"],"tags":["github","integration","data-validation"],"backgroundTag":"invalid-installation-id","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}