actualbudget/actual · warning

not-all-deleted

not-all-deleted

Error message

not-all-deleted

What it means

DELETE /users compares `ids.length` with `totalDeleted` returned by the deletion service. If any id could not be deleted (no matching row, constraints, or an internal failure), the handler responds 400 with reason 'not-all-deleted' and an empty details string, signaling a partial deletion instead of full success.

Source

Thrown at packages/sync-server/src/app-admin.js:179

  const { ids } = req.body || {};
  let totalDeleted = 0;
  ids.forEach(item => {
    const ownerId = UserService.getOwnerId();

    if (item === ownerId) return;

    UserService.deleteUserAccess(item);
    UserService.transferAllFilesFromUser(ownerId, item);
    const usersDeleted = UserService.deleteUser(item);
    totalDeleted += usersDeleted;
  });

  if (ids.length === totalDeleted) {
    res
      .status(200)
      .send({ status: 'ok', data: { someDeletionsFailed: false } });
  } else {
    res.status(400).send({
      status: 'error',
      reason: 'not-all-deleted',
      details: '',
    });
  }
});

app.get('/access', validateSessionMiddleware, (req, res) => {
  const fileId = req.query.fileId;

  const { granted } = UserService.checkFilePermission(
    fileId,
    res.locals.user_id,
  ) || {
    granted: 0,
  };

  if (granted === 0 && !isAdmin(res.locals.user_id)) {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Re-list users via the admin API and retry deletion with only ids that still exist.
  2. Treat the request as partially successful: query which users remain and delete them individually to surface the failing id.
  3. Refresh the client's user list cache before building the ids array.
  4. Make batch deletions idempotent — ignore 'missing' ids on retry.

Example fix

// before
await deleteUsers({ ids: staleIds }); // some ids already gone
// after
const users = await listUsers();
const liveIds = ids.filter(id => users.some(u => u.id === id));
if (liveIds.length) await deleteUsers({ ids: liveIds });
Defensive patterns

Strategy: validation

Validate before calling

const users = await listUsers();
const liveIds = ids.filter(id => users.some(u => u.id === id));
if (liveIds.length !== ids.length) {
  console.warn(`Skipping ${ids.length - liveIds.length} ids not present on server`);
}
if (liveIds.length) await deleteUsers({ ids: liveIds });

Try / catch

try {
  const res = await fetch(base + '/users', { method: 'DELETE', ... });
  const body = await res.json();
  if (body.reason === 'not-all-deleted') {
    // re-list users, delete remaining matching ids one by one to isolate failures
  }
} catch (e) { /* transport error */ }

Prevention

When it happens

Trigger: DELETE /users (admin session) where the ids array contains at least one id absent from the users table (already deleted, mistyped, or from another environment), so totalDeleted < ids.length.

Common situations: Batch cleanup scripts built from stale user lists; double-invoked deletion where the second run's ids no longer exist; mixed-environment ids (staging ids sent to production); ids collected manually with typos.

Related errors


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