actualbudget/actual · error
file-not-found
file-not-found
Error message
file-not-found
What it means
When the fileId is valid but filesService.get cannot find the file (throws FileNotFound), verifyFileExists responds 400 with the caller-supplied errorObject whose code is 'file-not-found'. Comments in the source note this should ideally be 404; it is kept 400 for frontend compatibility.
Source
Thrown at packages/sync-server/src/app-sync.ts:112
const verifyFileExists = (
fileId: unknown,
filesService: FilesService,
res: Response,
errorObject: string | Record<string, unknown>,
) => {
if (typeof fileId !== 'string' || !isValidFileId(fileId)) {
res.status(400).send('invalid fileId');
return;
}
try {
return filesService.get(fileId);
} catch (e) {
if (e instanceof FileNotFound) {
//FIXME: error code should be 404. Need to make sure frontend is ok with it.
//TODO: put this into a middleware that checks if FileNotFound is thrown and returns 404 and same error message
// for every FileNotFound error
res.status(400).send(errorObject);
return;
}
throw e;
}
};
function requireFileOwner(file: File, userId: string) {
const isOwner = file.owner === userId;
const isServerAdmin = isAdmin(userId);
if (isOwner || isServerAdmin) {
return null;
}
return 'file-access-not-allowed';
}
function requireFileAccess(file: File, userId: string) {
if (requireFileOwner(file, userId) === null) {
return null;View on GitHub (pinned to d4334cb6e6)
Solutions
- Verify the server URL matches the environment that owns the budget, then re-upload/re-create the budget or restore from backup.
- List files on the server and use an existing fileId.
- If the budget is gone, create a new one and let the client upload it (full sync) instead of syncing the stale id.
Example fix
// before
await fetch(`${serverUrl}/sync/file?fileId=${oldFileId}`); // 400 file-not-found
// after: check existence first
const files = await (await fetch(`${serverUrl}/files`, { headers: authHeaders })).json();
const exists = files.data.some(f => f.fileId === oldFileId);
if (!exists) await actual.createBudget({}); // or pick an existing fileId Defensive patterns
Strategy: validation
Validate before calling
const files = await (await fetch(serverUrl + '/files', { headers: authHeaders })).json();
if (!files.data.some(f => f.fileId === fileId)) throw new Error('file ' + fileId + ' does not exist on this server'); Try / catch
try {
const res = await fetch(`${serverUrl}/sync/file?fileId=${fileId}`, { headers: authHeaders });
if (res.status === 400) {
const err = await res.json();
if (err.code === 'file-not-found' || err.reason === 'file-not-found') throw new Error('budget missing on server: re-upload or restore it');
}
return res;
} catch (e) { throw e; } Prevention
- Confirm the sync-server URL points at the environment that owns the budget
- Restore/import budgets after migrating to a fresh server
- Handle deleted budgets by re-creating and performing a full upload
When it happens
Trigger: A /sync/file, /sync/currentfile, or /sync call referencing a fileId that does not exist on this server — deleted budget, wrong server environment, or a file id from a different instance.
Common situations: Pointing a client at a fresh sync-server without importing/restoring the budget; switching between self-hosted and the cloud server; a budget deleted while another device still syncs it.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Duplicate headers encountered for key ${key}
- invalid fileId
- Sync ID is required for sync ${flag}. Set --sync-id or ACTUA
- Could not resolve on-disk budget id for syncId ${syncId} aft
- TrieNode for key ${k} could not be found
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/f7230edece3c4435.
Report an issue: GitHub.