actualbudget/actual · error

User not found

Error message

User not found

What it means

HTTP 400 from GET /validate with `reason:'User not found'`. The session token was cryptographically valid, but `getUserInfo(session.user_id)` returned no row — i.e. the user behind the token was deleted from the account database after the token was issued. The endpoint refuses to validate a session whose backing user no longer exists.

Source

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

  const { prefs } = req.body || {};

  if (!prefs || typeof prefs !== 'object') {
    res.status(400).send({ status: 'error', reason: 'invalid-prefs' });
    return;
  }

  setServerPrefs(prefs);

  res.send({ status: 'ok', data: {} });
});

app.get('/validate', (req, res) => {
  const session = validateSession(req, res);
  if (session) {
    const user = getUserInfo(session.user_id);
    if (!user) {
      res.status(400).send({ status: 'error', reason: 'User not found' });
      return;
    }

    res.send({
      status: 'ok',
      data: {
        validated: true,
        userName: user?.user_name,
        permission: user?.role,
        userId: session?.user_id,
        displayName: user?.display_name,
        loginMethod: session?.auth_method,
        prefs: getServerPrefs(),
      },
    });
  }
});

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Log in again to obtain a fresh token for an existing user.
  2. Check the users list (admin GET /users) to confirm the user still exists; re-create the user if it was deleted unintentionally.
  3. Clear the client's cached token when users are removed server-side so it doesn't keep validating a dead session.

Example fix

// before: blindly reusing a stored token
const res = await api.get('/validate', { headers: { 'X-ACTUAL-TOKEN': storedToken } });
// after: re-login when validation reports a missing user
if (res.data?.reason === 'User not found') {
  const { token } = await login(username, password);
  storedToken = token;
}
Defensive patterns

Strategy: fallback

Validate before calling

// a token can only be validated server-side, but you can detect the stale-user case from the response
if (validateRes.data?.reason === 'User not found') invalidateStoredToken();

Type guard

function isUserMissing(res) {
  return res?.status === 'error' && res.reason === 'User not found';
}

Try / catch

try {
  const res = await get('/validate', { headers: authHeaders(token) });
  if (res.data.data.validated) return res.data.data;
  throw new SessionError(res.data.reason);
} catch (e) {
  if (isUserMissing(e.response?.data)) {
    clearToken();
    return startLoginFlow(); // fallback: re-login
  }
  throw e;
}

Prevention

When it happens

Trigger: GET /validate with a token whose user_id was deleted (DELETE /users or direct DB removal) between login and validation; stale tokens persisted by a client across a server user wipe.

Common situations: Admin deletes users while their clients are still running; restoring an old account.sqlite backup that lacks recently created users; DB re-creation during server re-bootstrap while clients keep cached tokens.

Related errors


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