different-ai/openwork · error · PluginArchRouteFailure

github_install_org_mismatch

github_install_org_mismatch

Error message

GitHub install state does not match the current organization.

What it means

Thrown (409) in completeGithubConnectorInstall when the signed install `state` parameter's embedded orgId does not match the organization in the current actor context. The state is a one-time payload minted at install start; completing the callback in a different organization than the one that initiated it is rejected.

Source

Thrown at ee/apps/den-api/src/routes/org/plugin-system/store.ts:6896

    wrapGithubConnectorError(error)
  }
  const state = createGithubInstallStateToken({
    orgId: input.context.organizationContext.organization.id,
    returnPath,
    secret: env.betterAuthSecret,
    userId: input.context.organizationContext.currentMember.userId,
  })

  return {
    redirectUrl: buildGithubAppInstallUrl({ app, state }),
    state,
  }
}

export async function completeGithubConnectorInstall(input: { context: PluginArchActorContext; installationId: number; state: string }) {
  const parsedState = consumeGithubInstallState(input.state)
  if (parsedState.orgId !== input.context.organizationContext.organization.id) {
    throw new PluginArchRouteFailure(409, "github_install_org_mismatch", "GitHub install state does not match the current organization.")
  }
  if (parsedState.userId !== input.context.organizationContext.currentMember.userId) {
    throw new PluginArchRouteFailure(409, "github_install_user_mismatch", "GitHub install state does not match the current user.")
  }

  const connectorAccount = await upsertGithubConnectorAccountFromInstallation({
    context: input.context,
    installationId: input.installationId,
  })

  return {
    connectorAccount,
    // Keep install completion fast. The connected-account screen loads repositories next.
    repositories: [],
  }
}

export async function getGithubConnectorDiscovery(input: { connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext }) {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Restart the install from the organization where you want the connector: call startGithubConnectorInstall in that org's context and use the fresh state.
  2. Ensure the client keeps the active organization stable across the install round trip (don't switch orgs mid-flow).
  3. If building automation, pass the same organization context to both start and complete calls.

Example fix

// before
// started install in org A, callback handled under org B
await completeGithubConnectorInstall({ installationId, state }) // 409 github_install_org_mismatch
// after
switchOrganizationContext(orgA) // restore the org that initiated the install
await completeGithubConnectorInstall({ installationId, state })
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await completeGithubConnectorInstall({ installationId, state })
} catch (e) {
  if (e instanceof PluginArchRouteFailure && e.code === 'github_install_org_mismatch') {
    await restartInstallInCurrentOrg() // mint fresh state bound to the active org
  } else throw e
}

Prevention

When it happens

Trigger: Starting a GitHub install in org A, then completing the callback while signed into org B; replaying an install-state token from another org; user switching active org between install start and the GitHub redirect back.

Common situations: Users belonging to multiple organizations click the GitHub callback link from a bookmark or another browser tab where their active org changed; reusing an old install URL after switching workspaces.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/94259d43e13d4281. Report an issue: GitHub.