{"record":{"id":"aab79a4fa2c17fca","repo":"actualbudget/actual","slug":"already-bootstraped","errorCode":"already-bootstraped","errorMessage":"already-bootstraped","messagePattern":"already-bootstraped","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/sync-server/src/app-openid.ts","lineNumber":71,"sourceCode":"      details: 'permission-not-found',\n    });\n    return;\n  }\n\n  const { error } = (await disableOpenID(req.body)) || {};\n\n  if (error) {\n    res.status(401).send({ status: 'error', reason: error });\n    return;\n  }\n  res.send({ status: 'ok' });\n});\n\napp.post('/config', openIdConfigRateLimiter, async (req, res) => {\n  const ownerCount = UserService.getOwnerCount();\n\n  if (ownerCount > 0) {\n    res.status(400).send({ status: 'error', reason: 'already-bootstraped' });\n    return;\n  }\n\n  if (!(await checkPassword(req.body.password))) {\n    res.status(400).send({ status: 'error', reason: 'invalid-password' });\n    return;\n  }\n\n  const auth = UserService.getOpenIDConfig();\n\n  if (!auth) {\n    res\n      .status(500)\n      .send({ status: 'error', reason: 'OpenID configuration not found' });\n    return;\n  }\n\n  try {","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-openid.ts#L53-L89","documentation":"POST /openid/config bootstraps OpenID configuration and is only allowed while the server has no owner yet. If UserService.getOwnerCount() is greater than zero, the endpoint responds 400 with reason 'already-bootstraped'. This prevents reconfiguring the authentication method after the server has been set up.","triggerScenarios":"Calling POST /openid/config on a sync-server that already has at least one owner user (i.e., was already bootstrapped with a password/method).","commonSituations":"Running setup automation twice against the same server; trying to change OpenID settings via /config after initial setup (the correct route is POST /openid by an admin); pointing a fresh provisioning script at an existing server with existing users.","solutions":["Use POST /openid (admin-authenticated) to change OpenID settings on an already-bootstrapped server","If the server must be re-bootstrapped, reset it to a clean state (delete/reset the account data) so ownerCount is 0","Skip the /config call in automation when the server already has owners (probe via an endpoint or check first)"],"exampleFix":"// before\ncurl -X POST /openid/config -d '{...}'   # 400 already-bootstraped\n// after (existing server)\ncurl -X POST /openid -d '{...}' -b admin-session-cookie","handlingStrategy":"fallback","validationCode":"// probe whether the server already has owners before calling /config\nconst probe = await fetch(baseUrl + '/openid/config', { method: 'HEAD' });\n// or track bootstrap state in your provisioning tool and skip if already done","typeGuard":null,"tryCatchPattern":"if (res.status === 400 && (await res.json()).reason === 'already-bootstraped') {\n  // fall back to admin-authenticated POST /openid to change settings instead\n}","preventionTips":["Make provisioning scripts idempotent: check before bootstrapping","Use POST /openid (admin) for changes after initial setup","Never re-run first-time setup automation against an existing server","Point new-server automation at a fresh instance/data directory"],"tags":["openid","http-400","bootstrap","configuration","state-conflict"],"backgroundTag":"already-initialized","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}