actualbudget/actual · error
New owner not found
Error message
New owner not found
What it means
transferAllFilesFromUser wraps all file-reassignment work in a database transaction and first verifies the target owner exists via getUserById. If no user row matches the supplied ownerId, it throws 'New owner not found' to abort the transfer before any files are reassigned. The transaction rolls back, so no partial ownership changes occur.
Source
Thrown at packages/sync-server/src/services/user-service.ts:117
export function deleteUserAccess(userId) {
try {
return getAccountDb().mutate('DELETE FROM user_access WHERE user_id = ?', [
userId,
]).changes;
} catch (error) {
throw new Error(`Failed to delete user access: ${error.message}`);
}
}
export function transferAllFilesFromUser(ownerId, oldUserId) {
if (!ownerId || !oldUserId) {
throw new Error('Invalid user IDs');
}
try {
getAccountDb().transaction(() => {
const ownerExists = getUserById(ownerId);
if (!ownerExists) {
throw new Error('New owner not found');
}
getAccountDb().mutate('UPDATE files set owner = ? WHERE owner = ?', [
ownerId,
oldUserId,
]);
});
} catch (error) {
throw new Error(`Failed to transfer files: ${error.message}`);
}
}
export function updateFileOwner(ownerId, fileId) {
if (!ownerId || !fileId) {
throw new Error('Invalid parameters');
}
try {
const result = getAccountDb().mutate(
'UPDATE files set owner = ? WHERE id = ?',View on GitHub (pinned to d4334cb6e6)
Solutions
- Verify the ownerId exists before calling: run getUserById(ownerId) and create/insert the user if null
- Check you are passing the user's id (users.id), not the userName or displayName
- Confirm the sync server is pointed at the same account database that contains the owner user (check the correct server/data dir)
- If the owner was deleted, recreate the user or pick a different existing owner id
Example fix
// before
await transferAllFilesFromUser(subFromToken, oldUserId);
// after
const ownerId = getUserByUsername(subFromToken);
if (!ownerId) throw new Error(`Owner ${subFromToken} not provisioned yet`);
await transferAllFilesFromUser(ownerId, oldUserId); Defensive patterns
Strategy: validation
Validate before calling
import { getUserById } from './services/user-service';
function assertOwnerExists(ownerId) {
if (!getUserById(ownerId)) {
throw new Error(`Cannot transfer: owner ${ownerId} does not exist`);
}
}
assertOwnerExists(ownerId); Type guard
function isExistingUser(userId) {
return typeof userId === 'string' && userId.length > 0 && getUserById(userId) !== null;
} Try / catch
try {
transferAllFilesFromUser(ownerId, oldUserId);
} catch (e) {
if (e.message.includes('New owner not found')) {
logger.error(`Owner ${ownerId} missing; provision it before transferring`);
}
throw e;
} Prevention
- Provision the OpenID user (insert into users) before any file transfer in login finalization
- Always pass users.id, never userName or display name
- Look up the owner with getUserById immediately before transferring to avoid TOCTOU deletions
- Confirm the server points at the intended account.sqlite
When it happens
Trigger: Calling transferAllFilesFromUser(ownerId, oldUserId) with an ownerId that has no row in the users table — e.g. a deleted or never-created user id, a stale id from a different database, or a caller (like loginWithOpenIdFinalize) resolving the owner from a token/sub claim that was not yet inserted as a user.
Common situations: OpenID login flows where the identity provider's subject does not match any local user record; passing a userName instead of a user id; referencing a user deleted by another admin between fetching and transferring; pointing at the wrong account.sqlite database.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- Failed to transfer files: ${error.message}
- openid-grant-failed
- Could not update File
- File not found
- Invalid file ID
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/71dccfb465f35669.
Report an issue: GitHub.