{"record":{"id":"b7570fe51db3baac","repo":"different-ai/openwork","slug":"github-install-user-mismatch","errorCode":"github_install_user_mismatch","errorMessage":"GitHub install state does not match the current user.","messagePattern":"GitHub install state does not match the current user\\.","errorType":"http","errorClass":"PluginArchRouteFailure","httpStatus":409,"severity":"error","filePath":"ee/apps/den-api/src/routes/org/plugin-system/store.ts","lineNumber":6899,"sourceCode":"    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 }) {\n  const discovery = await resolveGithubConnectorDiscovery(input)\n  return {\n    autoImportNewPlugins: discovery.autoImportNewPlugins,","sourceCodeStart":6881,"sourceCodeEnd":6917,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/routes/org/plugin-system/store.ts#L6881-L6917","documentation":"Thrown (409) in completeGithubConnectorInstall when the parsed install state's userId differs from `currentMember.userId` in the actor context. GitHub install callbacks are bound to the initiating user; completing as a different user in the same org is rejected to prevent cross-user install hijacking.","triggerScenarios":"User A starts the install, user B (same org admin) completes the callback; the session changed users between start and callback (re-login as someone else); replaying another user's install completion URL.","commonSituations":"Shared admin accounts or password managers swapping sessions mid-flow; forwarding the GitHub redirect URL to a teammate to 'finish the install'; concurrent installs by two admins with mixed-up state params.","solutions":["Have the original initiating user complete the callback, or start a new install while logged in as the current user.","Do not share or forward install completion URLs between users; each user should run their own install flow.","If automating, keep the same authenticated session for both startGithubConnectorInstall and completeGithubConnectorInstall."],"exampleFix":"// before\n// state minted for user A; current session is user B\nawait completeGithubConnectorInstall({ installationId, state }) // 409 github_install_user_mismatch\n// after\nawait signInAs(stateInitiatingUserId)\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_user_mismatch') {\n    await notifyOriginalUserToCompleteInstall() // or start a fresh install as current user\n  } else throw e\n}","preventionTips":["Keep the same authenticated session for the whole install round trip","Never forward GitHub callback URLs to other users","Detect session user changes before the callback and restart the flow"],"tags":["github","state-mismatch","csrf","auth"],"backgroundTag":"oauth-state-mismatch","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}