actualbudget/actual · error
Failed to transfer files: ${error.message}
Error message
Failed to transfer files: ${error.message} What it means
transferAllFilesFromUser catches any error raised inside its try block — including the 'Invalid user IDs' and 'New owner not found' errors it throws itself — and rethrows it wrapped as `Failed to transfer files: <original message>`. This wrapper indicates the ownership-transfer transaction did not complete. The original cause is preserved in the message suffix.
Source
Thrown at packages/sync-server/src/services/user-service.ts:125
}
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 = ?',
[ownerId, fileId],
);
if (result.changes === 0) {
throw new Error('File not found');
}
} catch (error) {
throw new Error(`Failed to update file owner: ${error.message}`);
}View on GitHub (pinned to d4334cb6e6)
Solutions
- Read the full message after the colon to identify the underlying cause ('Invalid user IDs', 'New owner not found', or a SQLite error)
- If the cause is 'New owner not found', provision/verify the owner user before the transfer call
- If the cause is a SQLite error, check database file permissions, disk space, and that no other process holds a write lock
- Call transferAllFilesFromUser inside your own try/catch and log error.message fully rather than truncating it
Example fix
// before
await transferAllFilesFromUser(ownerId, oldUserId); // opaque wrapped error
// after
try {
await transferAllFilesFromUser(ownerId, oldUserId);
} catch (e) {
logger.error('transfer failed, cause:', e.message); // shows 'Failed to transfer files: New owner not found'
if (e.message.includes('New owner not found')) {
await provisionUser(ownerId);
await transferAllFilesFromUser(ownerId, oldUserId);
}
} Defensive patterns
Strategy: try-catch
Validate before calling
function canTransfer(ownerId, oldUserId) {
return Boolean(ownerId) && Boolean(oldUserId) && getUserById(ownerId) !== null;
}
if (!canTransfer(ownerId, oldUserId)) throw new Error('precheck failed'); Type guard
function isTransferable(ownerId, oldUserId) {
return typeof ownerId === 'string' && ownerId.length > 0 &&
typeof oldUserId === 'string' && oldUserId.length > 0;
} Try / catch
try {
transferAllFilesFromUser(ownerId, oldUserId);
} catch (e) {
const cause = e.message.replace('Failed to transfer files: ', '');
logger.error({ cause }, 'file transfer failed');
if (cause.includes('New owner not found')) await provisionAndRetry(ownerId, oldUserId);
else throw e;
} Prevention
- Log the full error.message — the root cause is after the wrapper prefix
- Validate both ids and owner existence before calling
- Run transfers during low-write windows to avoid SQLite lock contention
- Wrap retries around the specific cause, not the wrapper text
When it happens
Trigger: Any failure inside the function: falsy ownerId/oldUserId ('Invalid user IDs'), missing owner ('New owner not found'), or an underlying SQLite error during the UPDATE files statement (locked db, disk error, schema mismatch).
Common situations: Seeing only the generic prefix in logs and missing the real cause; OpenID finalize handlers hitting 'New owner not found' wrapped here; a locked or read-only account database producing SQLite errors wrapped here.
Related errors
- New owner not found
- Failed to update file owner: ${error.message}
- Failed to add user access: ${error.message}
- openid-grant-failed
- Could not update File
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/194c7deb16f6d9bf.
Report an issue: GitHub.