actualbudget/actual · error
The provided userIds must be a non-empty array.
Error message
The provided userIds must be a non-empty array.
What it means
deleteUserAccessByFileId requires userIds to be a non-empty array and throws this error otherwise. The function builds an IN (...) clause from the array, so an empty or non-array value would produce invalid SQL or a pointless transaction. The check happens before any database work.
Source
Thrown at packages/sync-server/src/services/user-service.ts:207
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.');
}
const CHUNK_SIZE = 999;
let totalChanges = 0;
try {
getAccountDb().transaction(() => {
for (let i = 0; i < userIds.length; i += CHUNK_SIZE) {
const chunk = userIds.slice(i, i + CHUNK_SIZE);
const placeholders = chunk.map(() => '?').join(',');
const sql = `DELETE FROM user_access WHERE user_id IN (${placeholders}) AND file_id = ?`;
const result = getAccountDb().mutate(sql, [...chunk, fileId]);
totalChanges += result.changes;
}
});
} catch (error) {View on GitHub (pinned to d4334cb6e6)
Solutions
- Always pass an array of user ids, e.g. deleteUserAccessByFileId([userId], fileId)
- Check userIds.length > 0 at the call site and skip the call when nothing needs deleting
- Update callers still using the old single-id signature
- Return early (or a 204) when the batch is empty instead of treating it as an error
Example fix
// before
deleteUserAccessByFileId(userId, fileId); // throws: not an array
// after
const userIds = Array.isArray(userId) ? userId : [userId];
if (userIds.length > 0) {
deleteUserAccessByFileId(userIds, fileId);
} Defensive patterns
Strategy: validation
Validate before calling
function assertNonEmptyArray(value, name) {
if (!Array.isArray(value) || value.length === 0) {
throw new Error(`${name} must be a non-empty array`);
}
}
assertNonEmptyArray(userIds, 'userIds'); Type guard
function isUserIdArray(value) {
return Array.isArray(value) && value.length > 0 &&
value.every(id => typeof id === 'string' && id.length > 0);
} Try / catch
try {
deleteUserAccessByFileId(userIds, fileId);
} catch (e) {
if (e.message.includes('must be a non-empty array')) {
logger.warn('Nothing to revoke; skipping');
return 0;
}
throw e;
} Prevention
- Wrap single ids in an array before calling
- Skip the call (or return 204) when the computed batch is empty
- Update callers still on the old (userId, fileId) signature
- Validate request bodies normalize single ids into arrays at the API layer
When it happens
Trigger: Calling deleteUserAccessByFileId(userIds, fileId) with a single id string instead of an array, an empty array after filtering, undefined/null when a caller had nothing to delete, or a destructured variable that is not an array.
Common situations: Scripts passing one userId without wrapping it in an array; batch-revocation jobs computing an empty selection and still calling the function; API callers sending a single id in the request body; refactors changing the signature from (userId, fileId) to (userIds, fileId).
Understand the failure class
Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.
Related errors
- Invalid parameters
- Invalid --name: must be a non-empty string.
- No update fields provided. Use --name or --offbudget.
- Invalid cutoff date: expected a valid date (e.g. YYYY-MM-DD)
- No update fields provided. Use --name or --hidden.
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/490b8ad577e13f39.
Report an issue: GitHub.