actualbudget/actual · error

User ID is required for file creation

Error message

User ID is required for file creation

What it means

Thrown inline in the file-creation handler (POST /files) of app-sync.ts when the authenticated request has no user_id on res.locals. The sync server requires every uploaded file to be owned by a known user, so if the auth middleware did not populate user_id, creation is aborted with this error instead of writing an orphaned file row.

Source

Thrown at packages/sync-server/src/app-sync.ts:384

    res.status(500).send({ status: 'error' });
    return;
  }

  if (!currentFile) {
    // it's new
    const newGroupId = generateGroupId();
    groupId = newGroupId;
    filesService.set(
      new File({
        id: fileId,
        groupId: newGroupId,
        syncVersion: syncFormatVersion,
        name,
        encryptMeta,
        owner:
          res.locals.user_id ||
          (() => {
            throw new Error('User ID is required for file creation');
          })(),
      }),
    );

    res.send({ status: 'ok', groupId });
    return;
  }

  if (!groupId) {
    // sync state was reset, create new group
    const newGroupId = generateGroupId();
    groupId = newGroupId;
    filesService.update(fileId, new FileUpdate({ groupId: newGroupId }));
  }

  // Regardless, update some properties
  filesService.update(
    fileId,

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Ensure the request carries a valid access token so the auth middleware sets res.locals.user_id.
  2. Verify the auth middleware is registered before the file-creation route and matches the method (header vs openid).
  3. If behind a proxy, confirm it forwards the Authorization header to the sync server.
  4. Check server logs/auth config (ENABLE_OPENID, trusted proxies) to see why authentication was skipped.

Example fix

// before
curl -X POST http://server/files -d '{...}'            // no token -> 500 'User ID is required...'
// after
curl -X POST http://server/files -H "Authorization: Bearer <token>" -d '{...}'
Defensive patterns

Strategy: validation

Validate before calling

// client-side, before POST /files
if (!accessToken) throw new Error('Not authenticated: no access token for file upload');
const res = await fetch(base + '/files', { headers: { Authorization: `Bearer ${accessToken}` }, ... });
if (res.status === 401 || res.status === 403) throw new Error('Auth middleware did not attach user identity');

Type guard

function hasUserId(locals: unknown): locals is { user_id: string } {
  return typeof locals === 'object' && locals !== null && typeof (locals as any).user_id === 'string' && (locals as any).user_id.length > 0;
}

Try / catch

try {
  await createFile(payload);
} catch (err) {
  if (err instanceof Error && err.message.includes('User ID is required')) {
    await reauthenticate();
    return createFile(payload);
  }
  throw err;
}

Prevention

When it happens

Trigger: POST to the file-upload endpoint while res.locals.user_id is empty — i.e. the request reached the handler without going through (or failing silently in) the auth middleware, or the token validation step did not attach the user identity.

Common situations: Deployments where the auth middleware is misconfigured or omitted from the route stack; self-hosted setups with a reverse proxy that strips the Authorization header; custom clients calling the endpoint without a valid token; version mismatches after upgrading the sync server.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/5a5ca3ea0bf754df. Report an issue: GitHub.