{"record":{"id":"6a64ce755fefb761","repo":"actualbudget/actual","slug":"user-or-file-not-found","errorCode":null,"errorMessage":"User or file not found","messagePattern":"User or file not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sync-server/src/services/user-service.ts","lineNumber":191,"sourceCode":"  return (\n    getAccountDb().first(\n      `SELECT 1 as granted\n       FROM files\n       WHERE files.id = ? and (files.owner = ?)`,\n      [fileId, userId],\n    ) || { granted: 0 }\n  );\n}\n\nexport function addUserAccess(userId, fileId) {\n  if (!userId || !fileId) {\n    throw new Error('Invalid parameters');\n  }\n  try {\n    const userExists = getUserById(userId);\n    const fileExists = getFileById(fileId);\n    if (!userExists || !fileExists) {\n      throw new Error('User or file not found');\n    }\n    getAccountDb().mutate(\n      'INSERT INTO user_access (user_id, file_id) VALUES (?, ?)',\n      [userId, fileId],\n    );\n  } catch (error) {\n    if (error.message.includes('UNIQUE constraint')) {\n      throw new Error('Access already exists');\n    }\n    throw new Error(`Failed to add user access: ${error.message}`);\n  }\n}\n\nexport function deleteUserAccessByFileId(userIds, fileId) {\n  if (!Array.isArray(userIds) || userIds.length === 0) {\n    throw new Error('The provided userIds must be a non-empty array.');\n  }\n","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/services/user-service.ts#L173-L209","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait addUserAccess(targetUserId, fileId);\n// after\nif (!getUserById(targetUserId)) {\n  return res.status(404).json({ error: `User ${targetUserId} does not exist` });\n}\nawait addUserAccess(targetUserId, fileId);","handlingStrategy":"validation","validationCode":"import { getUserById, getFileById } from './services/user-service';\nif (!getUserById(userId)) throw new Error(`User ${userId} not found`);\nif (!getFileById(fileId)) throw new Error(`File ${fileId} not found`);","typeGuard":"function canGrantAccess(userId, fileId) {\n  return getUserById(userId) !== null && getFileById(fileId) !== null;\n}","tryCatchPattern":"try {\n  addUserAccess(userId, fileId);\n} catch (e) {\n  if (e.message.includes('User or file not found')) {\n    return res.status(404).json({ error: 'Unknown user or file' });\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["sync-server","user-not-found","file-not-found","referential-integrity"],"backgroundTag":"entity-not-found","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}