actualbudget/actual · error
Duplicate headers encountered for key ${key}
Error message
Duplicate headers encountered for key ${key} What it means
extractSingleHeader reads a single request header by key; express delivers repeated headers as an array. When req.headers[key] is not a string (an array of duplicates), the helper responds 400 with 'Duplicate headers encountered for key ' + key and returns null.
Source
Thrown at packages/sync-server/src/app-sync.ts:88
function generateGroupId(): GroupId {
const id = uuidv4();
if (!isValidGroupId(id)) {
throw new TypeError('UUID format no longer matches expected format');
}
return id;
}
function extractSingleHeader(
req: Request,
res: Response,
key: string,
): string | null {
const value = req.headers[key];
if (!value) {
return null;
}
if (typeof value !== 'string') {
res.status(400).send('Duplicate headers encountered for key ' + key);
return null;
}
return value;
}
const verifyFileExists = (
fileId: unknown,
filesService: FilesService,
res: Response,
errorObject: string | Record<string, unknown>,
) => {
if (typeof fileId !== 'string' || !isValidFileId(fileId)) {
res.status(400).send('invalid fileId');
return;
}
try {
return filesService.get(fileId);View on GitHub (pinned to d4334cb6e6)
Solutions
- Inspect the outgoing request and remove the duplicated header so each X-ACTUAL-* key appears once.
- Fix the proxy/middleware that adds the header (e.g. nginx `proxy_set_header` on a header the client already sends).
- In the client, set headers via a single map assignment instead of appending multiple times.
Example fix
// before: duplicate X-ACTUAL-FILE-ID
headers.append('X-ACTUAL-FILE-ID', fileId); // fetch Headers.append allows duplicates
// after
headers.set('X-ACTUAL-FILE-ID', fileId); // replaces, guaranteeing a single value Defensive patterns
Strategy: validation
Validate before calling
const names = ['X-ACTUAL-FILE-ID','X-ACTUAL-GROUP-ID','X-ACTUAL-KEY-ID','X-ACTUAL-ENCRYPT-META','X-ACTUAL-FORMAT-VERSION'];
for (const n of names) {
const v = req.headers[n.toLowerCase()];
if (Array.isArray(v)) throw new Error('duplicate header: ' + n);
} Type guard
const isSingleHeader = (v) => typeof v === 'string';
Try / catch
try {
const res = await sendSyncRequest(headers);
if (res.status === 400 && (await res.text()).startsWith('Duplicate headers encountered')) throw new Error('remove duplicated X-ACTUAL-* headers');
return res;
} catch (e) { throw e; } Prevention
- Use Headers.set (not append) when building request headers
- Audit reverse-proxy configs for headers added on top of client-supplied ones
- Log outgoing headers once when debugging sync setups
When it happens
Trigger: An HTTP request to a sync route (X-ACTUAL-*) that contains the same custom header twice, e.g. from a misconfigured proxy adding the header when the client already sets it.
Common situations: Reverse proxies (nginx/traefik/Cloudflare workers) appending auth or encryption-key headers; HTTP/2 lowercase/duplicate header injection; test harnesses merging header maps with duplicates.
Related errors
- invalid fileId
- file-not-found
- Sync ID is required for sync ${flag}. Set --sync-id or ACTUA
- Could not resolve on-disk budget id for syncId ${syncId} aft
- TrieNode for key ${k} could not be found
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/35bf6671148ff483.
Report an issue: GitHub.