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 accessCount

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Inspect the suffix of the message to find the root cause before changing code
  2. For 'File not found', verify the fileId exists (see error 393 solutions)
  3. For SQLite errors, check file permissions, free disk space, and concurrent writer contention on account.sqlite
  4. 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

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


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