toeverything/AFFiNE · error · ActionForbidden
action_forbidden
action_forbidden
Error message
First user already created
What it means
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.
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.
Example fix
// before
await fetch('/api/selfhost/create-admin-user', { method: 'POST', body }); // second call -> action_forbidden
// after
const status = await api.getSetupStatus(); // mirrors server.initialized()
if (status.initialized) router.replace('/signin');
else await fetch('/api/selfhost/create-admin-user', { method: 'POST', body }); Defensive patterns
Strategy: try-catch
Validate before calling
const status = await api.getSetupStatus(); // mirrors server.initialized()
if (status.initialized) router.replace('/signin'); Type guard
const isActionForbidden = (e: unknown): e is ActionForbidden => e instanceof ActionForbidden;
Try / catch
try {
await api.createAdmin(input);
} catch (e) {
if (e instanceof ActionForbidden) return router.replace('/signin'); // setup already done
throw e;
} Prevention
- 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.
When it happens
Trigger: Calling /create-admin-user a second time, or hitting it on an instance (or shared database) whose initial setup already completed.
Common situations: 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.
Related errors
AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18).
Data as JSON: /api/errors/844eb44a7e664b0c.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/core/selfhost/controller.ts:43
export class CustomSetupController {
constructor(
private readonly config: Config,
private readonly models: Models,
private readonly auth: AuthService,
private readonly sessionIssuer: SessionIssuer,
private readonly mutex: Mutex,
private readonly server: ServerService
) {}
@Public()
@Post('/create-admin-user')
async createAdmin(
@Req() req: Request,
@Res() res: Response,
@Body() input: CreateUserInput
) {
if (await this.server.initialized()) {
throw new ActionForbidden('First user already created');
}
validators.assertValidEmail(input.email);
if (!input.password) {
throw new PasswordRequired();
}
validators.assertValidPassword(
input.password,
this.config.auth.passwordRequirements
);
await using lock = await this.mutex.acquire('createFirstAdmin');
if (!lock) {
throw new InternalServerError();
}View on GitHub (pinned to 2af30773ae)