actualbudget/actual · error
User or file not found
Error message
User or file not found
What it means
addUserAccess checks that both the user and the file actually exist (via getUserById and getFileById) and throws 'User or file not found' if either lookup returns null. This keeps the user_access join table free of dangling references. The insert never executes when this fires.
Source
Thrown at packages/sync-server/src/services/user-service.ts:191
return (
getAccountDb().first(
`SELECT 1 as granted
FROM files
WHERE files.id = ? and (files.owner = ?)`,
[fileId, userId],
) || { granted: 0 }
);
}
export function addUserAccess(userId, fileId) {
if (!userId || !fileId) {
throw new Error('Invalid parameters');
}
try {
const userExists = getUserById(userId);
const fileExists = getFileById(fileId);
if (!userExists || !fileExists) {
throw new Error('User or file not found');
}
getAccountDb().mutate(
'INSERT INTO user_access (user_id, file_id) VALUES (?, ?)',
[userId, fileId],
);
} catch (error) {
if (error.message.includes('UNIQUE constraint')) {
throw new Error('Access already exists');
}
throw new Error(`Failed to add user access: ${error.message}`);
}
}
export function deleteUserAccessByFileId(userIds, fileId) {
if (!Array.isArray(userIds) || userIds.length === 0) {
throw new Error('The provided userIds must be a non-empty array.');
}
View on GitHub (pinned to d4334cb6e6)
Solutions
- Call getUserById(userId) and getFileById(fileId) first; create the missing user or verify the file exists before sharing
- Confirm you are passing ids (users.id / files.id), not usernames or display names
- Refresh the user/file list from the server — stale client data often references deleted rows
- Verify both ids belong to the same account database / server instance
Example fix
// before
await addUserAccess(targetUserId, fileId);
// after
if (!getUserById(targetUserId)) {
return res.status(404).json({ error: `User ${targetUserId} does not exist` });
}
await addUserAccess(targetUserId, fileId); Defensive patterns
Strategy: validation
Validate before calling
import { getUserById, getFileById } from './services/user-service';
if (!getUserById(userId)) throw new Error(`User ${userId} not found`);
if (!getFileById(fileId)) throw new Error(`File ${fileId} not found`); Type guard
function canGrantAccess(userId, fileId) {
return getUserById(userId) !== null && getFileById(fileId) !== null;
} Try / catch
try {
addUserAccess(userId, fileId);
} catch (e) {
if (e.message.includes('User or file not found')) {
return res.status(404).json({ error: 'Unknown user or file' });
}
throw e;
} Prevention
- Refresh user and file lists from the server before sharing; stale ids reference deleted rows
- Pass ids, not usernames
- Recreate deleted users before re-sharing files with them
- Confirm both ids come from the same server/database instance
When it happens
Trigger: Sharing a file with a userId that has no row in users (deleted or never-provisioned user), or a fileId with no row in files (deleted file or wrong database). One of the two being valid is not enough — either missing triggers this.
Common situations: Sharing to a user whose account was disabled-and-deleted by another admin; typing a username where an id is expected; referencing a file id from a restored/rolled-back database; cross-environment testing with ids from a different server instance.
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
- File does not exist or you don't have access to it
- File not found
- New owner not found
- File not found
- No id returned from download.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/6a64ce755fefb761.
Report an issue: GitHub.