actualbudget/actual · error
user-already-exists
user-already-exists
Error message
User ${userName} already exists What it means
The POST /users admin handler checks `UserService.getUserByUsername(userName)` before creating an account. If a user with that username already exists, it responds 400 with reason 'user-already-exists' instead of creating a duplicate. Usernames are unique in the sync-server's user table.
Source
Thrown at packages/sync-server/src/app-admin.js:80
reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`,
details: `${!userName ? 'Username' : 'Role'} cannot be empty`,
});
return;
}
const roleIdFromDb = UserService.validateRole(role);
if (!roleIdFromDb) {
res.status(400).send({
status: 'error',
reason: 'role-does-not-exists',
details: 'Selected role does not exist',
});
return;
}
const userIdInDb = UserService.getUserByUsername(userName);
if (userIdInDb) {
res.status(400).send({
status: 'error',
reason: 'user-already-exists',
details: `User ${userName} already exists`,
});
return;
}
const userId = uuidv4();
UserService.insertUser(
userId,
userName,
displayName || null,
enabled ? 1 : 0,
);
res.status(200).send({ status: 'ok', data: { id: userId } });
});
View on GitHub (pinned to d4334cb6e6)
Solutions
- Pick a different username, or look up the existing user's id via the admin API and reuse it.
- Make provisioning scripts idempotent: query the user first and skip creation if it exists.
- If the old user should not exist, delete it via DELETE /users then retry creation.
- Disable the submit button / deduplicate requests in the UI to prevent double submission.
Example fix
// before
for (const u of users) await createUsers({ userName: u.name, password: u.password });
// after
for (const u of users) {
const existing = await getUserByUsername(u.name); // skip if found
if (!existing) await createUsers({ userName: u.name, password: u.password });
} Defensive patterns
Strategy: validation
Validate before calling
const existing = await getUserByUsername(userName); // admin API lookup
if (existing) {
console.log(`Skipping ${userName}: already exists (id=${existing.id})`);
} else {
await createUser({ userName, password, role });
} Try / catch
try {
await createUser({ userName, password, role });
} catch (e) {
if (e.status === 400 && e.reason === 'user-already-exists') {
// treat as success in idempotent provisioning
return;
}
throw e;
} Prevention
- Make all user-provisioning scripts idempotent (check-then-create).
- Wrap creation and response handling so a crash cannot hide a created user.
- Prevent double form submission with a disabled/pending state.
- Deduplicate imported user lists by username before creating.
When it happens
Trigger: POST /users (admin session) where the userName exactly matches an existing row in the users table, e.g. re-running a provisioning script, double-submitting a signup form, or after a prior partially-failed request that still created the user.
Common situations: Idempotency-unaware automation that re-provisions users on each deploy; users re-registering with a previously taken name; recovery from a crash between user creation and response delivery; migrations importing a user dump twice.
Related errors
- role-does-not-exists
- cannot-find-user-to-update
- user-already-have-access
- Access already exists
- boot?.error
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/777212f1bca42b07.
Report an issue: GitHub.