mastra-ai/mastra · error

GitHub installation is invalid.

Error message

GitHub installation is invalid.

What it means

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.

Source

Thrown at mastracode/factory/src/integrations/github/session-subscriptions.ts:185

export async function unsubscribeCurrentSessionFromPullRequest(
  requestContext: RequestContext,
  pullRequest: number | string,
  github: GithubIntegration,
) {
  const target = await resolveSessionTarget(requestContext, github);
  const number = parsePullRequest(pullRequest, target.repository.slug);
  await unsubscribeFromPullRequest(await subscriptionInput(target, number), github.integrationStorage);
  return number;
}

export async function upsertFactoryTriageComment(
  requestContext: RequestContext,
  input: { issueNumber: number; body: string },
  github: GithubIntegration,
) {
  const target = await resolveSessionTarget(requestContext, github);
  const installationId = Number(target.installation.externalId);
  if (!Number.isSafeInteger(installationId) || installationId <= 0) throw new Error('GitHub installation is invalid.');
  return serializeTriageComment(`${installationId}:${target.repository.externalId}:${input.issueNumber}`, () =>
    github.upsertFactoryTriageComment({
      installationId,
      repository: target.repository.slug,
      issueNumber: input.issueNumber,
      body: input.body,
    }),
  );
}

export async function refreshGithubToken(requestContext: RequestContext, github: GithubIntegration): Promise<void> {
  const target = await resolveSessionTarget(requestContext, github);
  // `GH_TOKEN` feeds the `gh` CLI, so a configured org PAT wins over a minted
  // installation token (which 403s on integration-restricted endpoints). The
  // workspace records which PAT kind the sandbox was provisioned with, so a
  // review-board sandbox keeps its reviewer token on refresh.
  const pat = await getGithubPat(
    () => github.integrationStorage,

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Re-connect the GitHub App installation for the org so a fresh installation row with a valid numeric externalId is stored
  2. Inspect the installation row in integration storage (installation.externalId) and fix/backfill the externalId to the numeric GitHub installation ID
  3. Verify the session's target repository belongs to the org whose GitHub installation is actually installed (not a partial install)
  4. Add a pre-check on Number(installation.externalId) before calling upsertFactoryTriageComment and surface a clear reconnect prompt

Example fix

// before
await upsertFactoryTriageComment(requestContext, { issueNumber: 12, body: 'triage' }, github);
// after
const target = await resolveSessionTarget(requestContext, github);
const installationId = Number(target.installation.externalId);
if (!Number.isSafeInteger(installationId) || installationId <= 0) {
  throw new Error('GitHub App installation is missing or invalid — reconnect the installation.');
}
await upsertFactoryTriageComment(requestContext, { issueNumber: 12, body: 'triage' }, github);
Defensive patterns

Strategy: validation

Validate before calling

const target = await resolveSessionTarget(requestContext, github);
const installationId = Number(target.installation.externalId);
if (!Number.isSafeInteger(installationId) || installationId <= 0) {
  throw new Error('GitHub installation not connected or invalid — reconnect the GitHub App.');
}

Type guard

function hasValidInstallation(t: { installation: { externalId: unknown } }): t is { installation: { externalId: string | number } } {
  const id = Number(t.installation.externalId);
  return Number.isSafeInteger(id) && id > 0;
}

Try / catch

try {
  await upsertFactoryTriageComment(requestContext, input, github);
} catch (err) {
  if ((err as Error).message === 'GitHub installation is invalid.') {
    promptGithubReconnect(requestContext);
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/bcd9cb96352aeda5. Report an issue: GitHub.