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
- 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
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
- 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
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
- Version-control repository not found.
- Version-control installation not found.
- MastraFactory: duplicate integration id '${integration.id}'
- MastraFactory: integration tool '${name}' from '${ownerId}'
- MastraFactory: integrations [${channelRegistrations.map(({ i
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/bcd9cb96352aeda5.
Report an issue: GitHub.