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
- Re-list users via the admin API and retry deletion with only ids that still exist.
- Treat the request as partially successful: query which users remain and delete them individually to surface the failing id.
- Refresh the client's user list cache before building the ids array.
- 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
- Build batch id lists from a fresh server listing, not stale caches.
- Design deletions to be idempotent and tolerant of missing ids.
- Avoid re-running the same delete batch without re-checking state.
- Delete users individually when you need per-id error attribution.
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.