{"record":{"id":"844eb44a7e664b0c","repo":"toeverything/AFFiNE","slug":"action-forbidden-844eb4","errorCode":"action_forbidden","errorMessage":"First user already created","messagePattern":"First user already created","errorType":"exception","errorClass":"ActionForbidden","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/selfhost/controller.ts","lineNumber":43,"sourceCode":"export class CustomSetupController {\n  constructor(\n    private readonly config: Config,\n    private readonly models: Models,\n    private readonly auth: AuthService,\n    private readonly sessionIssuer: SessionIssuer,\n    private readonly mutex: Mutex,\n    private readonly server: ServerService\n  ) {}\n\n  @Public()\n  @Post('/create-admin-user')\n  async createAdmin(\n    @Req() req: Request,\n    @Res() res: Response,\n    @Body() input: CreateUserInput\n  ) {\n    if (await this.server.initialized()) {\n      throw new ActionForbidden('First user already created');\n    }\n\n    validators.assertValidEmail(input.email);\n\n    if (!input.password) {\n      throw new PasswordRequired();\n    }\n\n    validators.assertValidPassword(\n      input.password,\n      this.config.auth.passwordRequirements\n    );\n\n    await using lock = await this.mutex.acquire('createFirstAdmin');\n\n    if (!lock) {\n      throw new InternalServerError();\n    }","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/core/selfhost/controller.ts#L25-L61","documentation":"ActionForbidden('First user already created') from the self-hosted POST /create-admin-user handler (packages/backend/server/src/core/selfhost/controller.ts:42). The endpoint bootstraps the very first admin; once server.initialized() reports an existing first user, every further call is refused.","triggerScenarios":"Calling /create-admin-user a second time, or hitting it on an instance (or shared database) whose initial setup already completed.","commonSituations":"Setup wizard re-run after a refresh, a redirect loop back to /create-admin-user, or pointing at the wrong database that is already initialized.","solutions":["Treat it as done: sign in with the admin you already created.","To grant admin later, promote an existing user via the administrator feature or the server CLI.","Verify you are connected to the intended instance/database before re-running setup."],"exampleFix":"// before\nawait fetch('/api/selfhost/create-admin-user', { method: 'POST', body }); // second call -> action_forbidden\n\n// after\nconst status = await api.getSetupStatus(); // mirrors server.initialized()\nif (status.initialized) router.replace('/signin');\nelse await fetch('/api/selfhost/create-admin-user', { method: 'POST', body });","handlingStrategy":"try-catch","validationCode":"const status = await api.getSetupStatus(); // mirrors server.initialized()\nif (status.initialized) router.replace('/signin');","typeGuard":"const isActionForbidden = (e: unknown): e is ActionForbidden =>\n  e instanceof ActionForbidden;","tryCatchPattern":"try {\n  await api.createAdmin(input);\n} catch (e) {\n  if (e instanceof ActionForbidden) return router.replace('/signin'); // setup already done\n  throw e;\n}","preventionTips":["Drive the setup wizard from the server's initialized flag so the endpoint is never called twice.","Disable the create-admin submit button while a request is in flight."],"tags":["selfhost","setup","admin","bootstrap"],"backgroundTag":"setup-already-completed","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}