{"record":{"id":"355f53adde6540a1","repo":"actualbudget/actual","slug":"invalid-user-ids","errorCode":null,"errorMessage":"Invalid user IDs","messagePattern":"Invalid user IDs","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sync-server/src/services/user-service.ts","lineNumber":111,"sourceCode":"    if (changes > 0) {\n      getAccountDb().mutate('DELETE FROM sessions WHERE user_id = ?', [userId]);\n    }\n    return changes;\n  });\n}\nexport function deleteUserAccess(userId) {\n  try {\n    return getAccountDb().mutate('DELETE FROM user_access WHERE user_id = ?', [\n      userId,\n    ]).changes;\n  } catch (error) {\n    throw new Error(`Failed to delete user access: ${error.message}`);\n  }\n}\n\nexport function transferAllFilesFromUser(ownerId, oldUserId) {\n  if (!ownerId || !oldUserId) {\n    throw new Error('Invalid user IDs');\n  }\n  try {\n    getAccountDb().transaction(() => {\n      const ownerExists = getUserById(ownerId);\n      if (!ownerExists) {\n        throw new Error('New owner not found');\n      }\n      getAccountDb().mutate('UPDATE files set owner = ? WHERE owner = ?', [\n        ownerId,\n        oldUserId,\n      ]);\n    });\n  } catch (error) {\n    throw new Error(`Failed to transfer files: ${error.message}`);\n  }\n}\n\nexport function updateFileOwner(ownerId, fileId) {","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/services/user-service.ts#L93-L129","documentation":"transferAllFilesFromUser(ownerId, oldUserId) validates both arguments up front and throws 'Invalid user IDs' if either ownerId or oldUserId is falsy (empty string, null, undefined). It is called from loginWithOpenIdFinalize to move all files off a duplicate account onto the logging-in user's account.","triggerScenarios":"OpenID login finalization where the resulting user id or the duplicate user's id is missing/empty — e.g. the OIDC provider returned no usable subject/user id, or the duplicate account lookup returned nothing but the code still attempted the transfer.","commonSituations":"Misconfigured OpenID providers not returning a stable unique claim; users logging in with different email casing creating empty/partial account records; custom integrations calling transferAllFilesFromUser with unset variables.","solutions":["Check that the OpenID provider returns a stable unique identifier (sub claim) mapped to Actual's user id.","Log both IDs before the transfer and confirm neither is empty in loginWithOpenIdFinalize.","Resolve the duplicate-account situation manually via the admin users API if the transfer keeps failing.","Fix user creation logic so accounts are never created with empty user ids."],"exampleFix":"// before\nawait transferAllFilesFromUser(newUser.user_id, dupUser.user_id); // dupUser may be null -> 'Invalid user IDs'\n// after\nif (newUser?.user_id && dupUser?.user_id) {\n  await transferAllFilesFromUser(newUser.user_id, dupUser.user_id);\n}","handlingStrategy":"validation","validationCode":"if (!ownerId || !oldUserId) {\n  throw new Error(`transferAllFilesFromUser requires both ids; got owner=${ownerId}, old=${oldUserId}`);\n}\n// safe to call:\nawait transferAllFilesFromUser(ownerId, oldUserId);","typeGuard":"function isTransferableUser(u: { user_id?: string } | null | undefined): u is { user_id: string } {\n  return u !== null && u !== undefined && typeof u.user_id === 'string' && u.user_id.length > 0;\n}","tryCatchPattern":"try {\n  await transferAllFilesFromUser(ownerId, oldUserId);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid user IDs') {\n    logger.error('OpenID login finalize produced an empty user id; check provider sub claim mapping');\n    throw new Error('Login failed: account identifiers missing');\n  }\n  throw err;\n}","preventionTips":["Verify the OIDC provider returns a stable sub claim and it is mapped to user_id.","Check for null users before invoking transfer logic in loginWithOpenIdFinalize.","Test OpenID login against your real provider, not only mocks.","Prevent account creation with empty user ids via a creation-time check."],"tags":["validation","openid","sync-server","user-management"],"backgroundTag":"missing-required-parameter","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}