actualbudget/actual · error
Failed to update file owner: ${error.message}
Error message
Failed to update file owner: ${error.message} What it means
updateFileOwner wraps every error from its try block — including its own 'Invalid parameters' and 'File not found' throws, and any SQLite failure — in `Failed to update file owner: <cause>`. It signals the ownership change did not happen; the original reason is appended to the message.
Source
Thrown at packages/sync-server/src/services/user-service.ts:142
} 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}`);
}
}
export function getUserAccess(fileId, userId, isAdmin) {
return getAccountDb().all(
`SELECT users.id as userId, user_name as userName, files.owner, display_name as displayName
FROM users
JOIN user_access ON user_access.user_id = users.id
JOIN files ON files.id = user_access.file_id
WHERE files.id = ? and (files.owner = ? OR 1 = ?)`,
[fileId, userId, isAdmin ? 1 : 0],
);
}
export function countUserAccess(fileId, userId) {
const { accessCount } =
getAccountDb().first(
`SELECT COUNT(*) as accessCountView on GitHub (pinned to d4334cb6e6)
Solutions
- Inspect the suffix of the message to find the root cause before changing code
- For 'File not found', verify the fileId exists (see error 393 solutions)
- For SQLite errors, check file permissions, free disk space, and concurrent writer contention on account.sqlite
- Catch the error at the call site and surface the full message to admins rather than a generic failure
Example fix
// before
try {
updateFileOwner(ownerId, fileId);
} catch (e) {
flash('Could not update owner');
}
// after
try {
updateFileOwner(ownerId, fileId);
} catch (e) {
if (e.message.includes('database is locked')) {
await retryWithBackoff(() => updateFileOwner(ownerId, fileId));
} else {
flash(`Could not update owner: ${e.message}`);
}
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!ownerId || !fileId) throw new Error('ownerId and fileId required');
if (!getFileById(fileId)) throw new Error(`File ${fileId} not found`); Type guard
function canUpdateOwner(ownerId, fileId) {
return Boolean(ownerId) && Boolean(fileId) && getFileById(fileId) !== null;
} Try / catch
try {
updateFileOwner(ownerId, fileId);
} catch (e) {
const cause = e.message.replace('Failed to update file owner: ', '');
if (cause.includes('database is locked')) {
await backoffRetry(() => updateFileOwner(ownerId, fileId), 3);
} else {
logger.error({ cause }, 'updateFileOwner failed');
throw e;
}
} Prevention
- Parse the cause from the wrapped message before choosing a recovery path
- Pre-validate ids and file existence to avoid the wrapper entirely
- Retry only transient SQLite errors (locked), never 'File not found'
- Monitor disk space and file permissions on the server data directory
When it happens
Trigger: Any failure within updateFileOwner: falsy arguments, zero-row UPDATE ('File not found'), or a database error from the mutate call (locked database, read-only file, disk I/O error).
Common situations: Logs showing only the wrapper and obscuring the real cause; SQLite 'database is locked' under concurrent sync-server writes; permission problems after moving the data directory; debugging why an ownership change silently failed in an admin UI.
Related errors
- Failed to add user access: ${error.message}
- Failed to delete user access: ${error.message}
- Failed to transfer files: ${error.message}
- Failed to retrieve owner count
- Could not update File
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/0366d38122c8f87c.
Report an issue: GitHub.