actualbudget/actual · error
Invalid user IDs
Error message
Invalid user IDs
What it means
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.
Source
Thrown at packages/sync-server/src/services/user-service.ts:111
if (changes > 0) {
getAccountDb().mutate('DELETE FROM sessions WHERE user_id = ?', [userId]);
}
return changes;
});
}
export function deleteUserAccess(userId) {
try {
return getAccountDb().mutate('DELETE FROM user_access WHERE user_id = ?', [
userId,
]).changes;
} catch (error) {
throw new Error(`Failed to delete user access: ${error.message}`);
}
}
export function transferAllFilesFromUser(ownerId, oldUserId) {
if (!ownerId || !oldUserId) {
throw new Error('Invalid user IDs');
}
try {
getAccountDb().transaction(() => {
const ownerExists = getUserById(ownerId);
if (!ownerExists) {
throw new Error('New owner not found');
}
getAccountDb().mutate('UPDATE files set owner = ? WHERE owner = ?', [
ownerId,
oldUserId,
]);
});
} catch (error) {
throw new Error(`Failed to transfer files: ${error.message}`);
}
}
export function updateFileOwner(ownerId, fileId) {View on GitHub (pinned to d4334cb6e6)
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.
Example fix
// before
await transferAllFilesFromUser(newUser.user_id, dupUser.user_id); // dupUser may be null -> 'Invalid user IDs'
// after
if (newUser?.user_id && dupUser?.user_id) {
await transferAllFilesFromUser(newUser.user_id, dupUser.user_id);
} Defensive patterns
Strategy: validation
Validate before calling
if (!ownerId || !oldUserId) {
throw new Error(`transferAllFilesFromUser requires both ids; got owner=${ownerId}, old=${oldUserId}`);
}
// safe to call:
await transferAllFilesFromUser(ownerId, oldUserId); Type guard
function isTransferableUser(u: { user_id?: string } | null | undefined): u is { user_id: string } {
return u !== null && u !== undefined && typeof u.user_id === 'string' && u.user_id.length > 0;
} Try / catch
try {
await transferAllFilesFromUser(ownerId, oldUserId);
} catch (err) {
if (err instanceof Error && err.message === 'Invalid user IDs') {
logger.error('OpenID login finalize produced an empty user id; check provider sub claim mapping');
throw new Error('Login failed: account identifiers missing');
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.
Related errors
- Invalid redirect URL
- openid-grant-failed
- Invalid file ID
- Invalid group ID
- Failed to delete user access: ${error.message}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/355f53adde6540a1.
Report an issue: GitHub.