{"record":{"id":"6b0ba85084477e27","repo":"actualbudget/actual","slug":"boot-error","errorCode":null,"errorMessage":"boot?.error","messagePattern":"boot\\?\\.error","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/sync-server/src/app-account.js","lineNumber":63,"sourceCode":"  res.send({\n    status: 'ok',\n    data: {\n      bootstrapped: !needsBootstrap(),\n      loginMethod:\n        availableLoginMethods.length === 1\n          ? availableLoginMethods[0].method\n          : getLoginMethod(),\n      availableLoginMethods,\n      multiuser: getActiveLoginMethod() === 'openid',\n    },\n  });\n});\n\napp.post('/bootstrap', authRateLimiter, async (req, res) => {\n  const boot = await bootstrap(req.body);\n\n  if (boot?.error) {\n    res.status(400).send({ status: 'error', reason: boot?.error });\n    return;\n  }\n  res.send({ status: 'ok', data: boot });\n});\n\napp.get('/login-methods', (req, res) => {\n  const methods = listLoginMethods();\n  res.send({ status: 'ok', methods });\n});\n\napp.post('/login', authRateLimiter, async (req, res) => {\n  const loginMethod = getLoginMethod(req);\n  console.log('Logging in via ' + loginMethod);\n  let tokenRes = null;\n  switch (loginMethod) {\n    case 'header': {\n      const headerVal = req.get('x-actual-password') || '';\n      const obfuscated =","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-account.js#L45-L81","documentation":"The sync-server /bootstrap endpoint returns HTTP 400 with { status: 'error', reason: boot.error } when the bootstrap operation (initial server setup) fails. boot.error is the raw reason string produced by the bootstrap module — e.g. the server was already configured or the data directory cannot be initialized.","triggerScenarios":"POST /bootstrap with a body that makes bootstrap() return { error }: server already bootstrapped, password/openid setup mismatches existing config, or the data directory cannot be created.","commonSituations":"Running setup against an already-configured server; incorrect password/openid settings during first-time setup; filesystem permission problems in the server data directory; Docker volume mounted read-only.","solutions":["Read the 'reason' field in the 400 response — it names the concrete bootstrap failure","If the server is already set up, skip /bootstrap and go straight to /login","Check that ACTUAL_DATA_DIR (or the default data dir) exists and is writable","For fresh setups, clear previous bootstrap state only if re-initialization is intended"],"exampleFix":"// client side\nconst res = await fetch('/bootstrap', { method: 'POST', body });\nif (res.status === 400) {\n  const { reason } = await res.json();\n  showSetupError(reason); // e.g. already-bootstrapped -> redirect to /login\n}","handlingStrategy":"try-catch","validationCode":"// check setup state before POST /bootstrap\nconst methods = await fetch('/login-methods').then(r => r.json());\nif (methods.setupComplete) {\n  // skip bootstrap; go to login\n}","typeGuard":"function isBootstrapFailure(res, body) {\n  return res.status === 400 && body?.status === 'error' && typeof body?.reason === 'string';\n}","tryCatchPattern":"const res = await fetch('/bootstrap', { method: 'POST', body });\nconst body = await res.json();\nif (res.status === 400 && body.status === 'error') {\n  showSetupError(body.reason); // e.g. already bootstrapped -> redirect to /login\n  return;\n}","preventionTips":["Detect an already-bootstrapped server before calling /bootstrap","Verify data-dir write permissions before first-time setup","Surface the response's reason string to users instead of a generic failure"],"tags":["sync-server","bootstrap","http-400"],"backgroundTag":"bootstrap-failed","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}