actualbudget/actual · error

boot?.error

Error message

boot?.error

What it means

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.

Source

Thrown at packages/sync-server/src/app-account.js:63

  res.send({
    status: 'ok',
    data: {
      bootstrapped: !needsBootstrap(),
      loginMethod:
        availableLoginMethods.length === 1
          ? availableLoginMethods[0].method
          : getLoginMethod(),
      availableLoginMethods,
      multiuser: getActiveLoginMethod() === 'openid',
    },
  });
});

app.post('/bootstrap', authRateLimiter, async (req, res) => {
  const boot = await bootstrap(req.body);

  if (boot?.error) {
    res.status(400).send({ status: 'error', reason: boot?.error });
    return;
  }
  res.send({ status: 'ok', data: boot });
});

app.get('/login-methods', (req, res) => {
  const methods = listLoginMethods();
  res.send({ status: 'ok', methods });
});

app.post('/login', authRateLimiter, async (req, res) => {
  const loginMethod = getLoginMethod(req);
  console.log('Logging in via ' + loginMethod);
  let tokenRes = null;
  switch (loginMethod) {
    case 'header': {
      const headerVal = req.get('x-actual-password') || '';
      const obfuscated =

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Read the 'reason' field in the 400 response — it names the concrete bootstrap failure
  2. If the server is already set up, skip /bootstrap and go straight to /login
  3. Check that ACTUAL_DATA_DIR (or the default data dir) exists and is writable
  4. For fresh setups, clear previous bootstrap state only if re-initialization is intended

Example fix

// client side
const res = await fetch('/bootstrap', { method: 'POST', body });
if (res.status === 400) {
  const { reason } = await res.json();
  showSetupError(reason); // e.g. already-bootstrapped -> redirect to /login
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check setup state before POST /bootstrap
const methods = await fetch('/login-methods').then(r => r.json());
if (methods.setupComplete) {
  // skip bootstrap; go to login
}

Type guard

function isBootstrapFailure(res, body) {
  return res.status === 400 && body?.status === 'error' && typeof body?.reason === 'string';
}

Try / catch

const res = await fetch('/bootstrap', { method: 'POST', body });
const body = await res.json();
if (res.status === 400 && body.status === 'error') {
  showSetupError(body.reason); // e.g. already bootstrapped -> redirect to /login
  return;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/6b0ba85084477e27. Report an issue: GitHub.