{"record":{"id":"94259d43e13d4281","repo":"different-ai/openwork","slug":"github-install-org-mismatch","errorCode":"github_install_org_mismatch","errorMessage":"GitHub install state does not match the current organization.","messagePattern":"GitHub install state does not match the current organization\\.","errorType":"http","errorClass":"PluginArchRouteFailure","httpStatus":409,"severity":"error","filePath":"ee/apps/den-api/src/routes/org/plugin-system/store.ts","lineNumber":6896,"sourceCode":"    wrapGithubConnectorError(error)\n  }\n  const state = createGithubInstallStateToken({\n    orgId: input.context.organizationContext.organization.id,\n    returnPath,\n    secret: env.betterAuthSecret,\n    userId: input.context.organizationContext.currentMember.userId,\n  })\n\n  return {\n    redirectUrl: buildGithubAppInstallUrl({ app, state }),\n    state,\n  }\n}\n\nexport async function completeGithubConnectorInstall(input: { context: PluginArchActorContext; installationId: number; state: string }) {\n  const parsedState = consumeGithubInstallState(input.state)\n  if (parsedState.orgId !== input.context.organizationContext.organization.id) {\n    throw new PluginArchRouteFailure(409, \"github_install_org_mismatch\", \"GitHub install state does not match the current organization.\")\n  }\n  if (parsedState.userId !== input.context.organizationContext.currentMember.userId) {\n    throw new PluginArchRouteFailure(409, \"github_install_user_mismatch\", \"GitHub install state does not match the current user.\")\n  }\n\n  const connectorAccount = await upsertGithubConnectorAccountFromInstallation({\n    context: input.context,\n    installationId: input.installationId,\n  })\n\n  return {\n    connectorAccount,\n    // Keep install completion fast. The connected-account screen loads repositories next.\n    repositories: [],\n  }\n}\n\nexport async function getGithubConnectorDiscovery(input: { connectorInstanceId: ConnectorInstanceId; context: PluginArchActorContext }) {","sourceCodeStart":6878,"sourceCodeEnd":6914,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/routes/org/plugin-system/store.ts#L6878-L6914","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Restart the install from the organization where you want the connector: call startGithubConnectorInstall in that org's context and use the fresh state.","Ensure the client keeps the active organization stable across the install round trip (don't switch orgs mid-flow).","If building automation, pass the same organization context to both start and complete calls."],"exampleFix":"// before\n// started install in org A, callback handled under org B\nawait completeGithubConnectorInstall({ installationId, state }) // 409 github_install_org_mismatch\n// after\nswitchOrganizationContext(orgA) // restore the org that initiated the install\nawait completeGithubConnectorInstall({ installationId, state })","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await completeGithubConnectorInstall({ installationId, state })\n} catch (e) {\n  if (e instanceof PluginArchRouteFailure && e.code === 'github_install_org_mismatch') {\n    await restartInstallInCurrentOrg() // mint fresh state bound to the active org\n  } else throw e\n}","preventionTips":["Never switch active organization between install start and callback","Treat install state as single-use, org-bound, and short-lived","Deep-link users back into the exact org context that started the install"],"tags":["github","state-mismatch","csrf","multi-tenant"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}