{"record":{"id":"777212f1bca42b07","repo":"actualbudget/actual","slug":"user-already-exists","errorCode":"user-already-exists","errorMessage":"User ${userName} already exists","messagePattern":"User (.+?) already exists","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/sync-server/src/app-admin.js","lineNumber":80,"sourceCode":"      reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`,\n      details: `${!userName ? 'Username' : 'Role'} cannot be empty`,\n    });\n    return;\n  }\n\n  const roleIdFromDb = UserService.validateRole(role);\n  if (!roleIdFromDb) {\n    res.status(400).send({\n      status: 'error',\n      reason: 'role-does-not-exists',\n      details: 'Selected role does not exist',\n    });\n    return;\n  }\n\n  const userIdInDb = UserService.getUserByUsername(userName);\n  if (userIdInDb) {\n    res.status(400).send({\n      status: 'error',\n      reason: 'user-already-exists',\n      details: `User ${userName} already exists`,\n    });\n    return;\n  }\n\n  const userId = uuidv4();\n  UserService.insertUser(\n    userId,\n    userName,\n    displayName || null,\n    enabled ? 1 : 0,\n  );\n\n  res.status(200).send({ status: 'ok', data: { id: userId } });\n});\n","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-admin.js#L62-L98","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nfor (const u of users) await createUsers({ userName: u.name, password: u.password });\n// after\nfor (const u of users) {\n  const existing = await getUserByUsername(u.name); // skip if found\n  if (!existing) await createUsers({ userName: u.name, password: u.password });\n}","handlingStrategy":"validation","validationCode":"const existing = await getUserByUsername(userName); // admin API lookup\nif (existing) {\n  console.log(`Skipping ${userName}: already exists (id=${existing.id})`);\n} else {\n  await createUser({ userName, password, role });\n}","typeGuard":null,"tryCatchPattern":"try {\n  await createUser({ userName, password, role });\n} catch (e) {\n  if (e.status === 400 && e.reason === 'user-already-exists') {\n    // treat as success in idempotent provisioning\n    return;\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["http-400","duplicate","sync-server","admin-api"],"backgroundTag":"duplicate-key-conflict","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}