toeverything/AFFiNE · error · InternalServerError
internal_server_error
internal_server_error
Error message
An internal error occurred.
What it means
InternalServerError raised at packages/backend/server/src/core/selfhost/controller.ts:59 when mutex.acquire('createFirstAdmin') returns no lock. The endpoint serializes first-admin creation with a mutex; when a concurrent request already holds it, the loser does not wait but fails here.
Solutions
- Retry once, serially: after the winner finishes, the check turns into ActionForbidden('First user already created'), which effectively means success.
- Guard the UI so setup submits exactly once.
Example fix
// before
button.onclick = () => api.createAdmin(form); // double click -> two concurrent posts
// after
button.disabled = true;
try { await api.createAdmin(form); }
catch (e) { if (isInternalServerError(e)) await checkInitialized(); }
finally { button.disabled = false; } Defensive patterns
Strategy: retry
Type guard
const isInternalServerError = (e: unknown): e is InternalServerError => e instanceof InternalServerError;
Try / catch
try {
await api.createAdmin(input);
} catch (e) {
if (e instanceof InternalServerError) {
await delay(1000);
if (await isInitialized()) return done(); // the concurrent winner finished
}
throw e;
} Prevention
- Single-flight the setup request in the UI (disable submit while pending).
- Treat a lost race as success by re-checking initialization instead of showing an error.
When it happens
Trigger: Two POST /create-admin-user requests racing (double click, retry, parallel setup scripts); the request that loses lock acquisition throws.
Common situations: Impatient double-submit on slow networks, the setup wizard retrying on timeout, or monitoring probes hitting the endpoint.
Related errors
AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18).
Data as JSON: /api/errors/ab08f730ea552b35.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/core/selfhost/controller.ts:60
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();
}
const user = await this.models.user.create({
name: input.name || undefined,
email: input.email,
password: input.password,
registered: true,
});
try {
await this.models.userFeature.add(
user.id,
'administrator',
'selfhost setup'
);
const issued = await this.auth.issueUser(
user.id,
this.sessionIssuer.target(req)View on GitHub (pinned to 2af30773ae)