actualbudget/actual · error

File not found

Error message

File not found

What it means

updateFileOwner runs an UPDATE on files and checks result.changes; if the UPDATE matched zero rows it throws 'File not found', meaning no file with the given id exists. This distinguishes a no-op update from a successful ownership change.

Source

Thrown at packages/sync-server/src/services/user-service.ts:139

        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}`);
  }
}

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) {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Confirm the fileId exists: getFileById(fileId) should return a row before updating
  2. Re-fetch the current file list from the server; if the file was deleted, remove the stale reference on the client
  3. Verify you are querying the same account database the file was created in
  4. Check for id mismatch (cloud file id vs local id) and use the id stored in the files table

Example fix

// before
await updateFileOwner(newOwnerId, fileId); // throws if deleted
// after
if (!getFileById(fileId)) {
  logger.warn(`File ${fileId} no longer exists; skipping owner update`);
  return;
}
await updateFileOwner(newOwnerId, fileId);
Defensive patterns

Strategy: validation

Validate before calling

import { getFileById } from './services/user-service';
if (!getFileById(fileId)) {
  throw new Error(`File ${fileId} does not exist; refresh file list`);
}

Type guard

function fileExists(fileId) {
  return typeof fileId === 'string' && fileId.length > 0 && getFileById(fileId) !== null;
}

Try / catch

try {
  updateFileOwner(ownerId, fileId);
} catch (e) {
  if (e.message.includes('File not found')) {
    logger.warn(`Skipping owner update for missing file ${fileId}`);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling updateFileOwner with a fileId that does not exist in the files table — deleted file, id from a different budget/database, typo'd or truncated id, or fileId pointing to a file the requesting server instance never synced.

Common situations: Client holds a stale file id after the file was deleted server-side; restoring a database from backup so newer file ids are gone; multi-instance setups where files live in a different account.sqlite; passing the internal cloud file id vs the local budget id interchangeably.

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


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