koala73/worldmonitor · error · ValidationError
duplicate id "${id}" — results would be ambiguous
Error message
duplicate id "${id}" — results would be ambiguous What it means
validateOperations tracks every operation id in a Set; a second operation with the same id (after trimming, and counting index-defaulted ids) is a FieldViolation 'duplicate id "<id>" — results would be ambiguous' and the entire batch is rejected with ValidationError. Duplicate ids would make it impossible to correlate a result row with its request, so the server refuses rather than guess.
Source
Thrown at server/worldmonitor/batch/v1/execute-batch.ts:208
// Recursion guard: the gateway forwards the marker untouched, so a batch
// arriving with it was issued BY a batch — refuse regardless of the
// per-path nested_batch check below.
if (ctx.request.headers.has(BATCH_MARKER_HEADER)) {
throw new ApiError(400, 'Nested batch requests are not allowed', '');
}
const operations = Array.isArray(req.operations) ? req.operations : [];
if (operations.length < 1 || operations.length > MAX_BATCH_OPERATIONS) {
throw new ValidationError([{
field: 'operations',
description: `must contain between 1 and ${MAX_BATCH_OPERATIONS} operations`,
}]);
}
const origin = new URL(ctx.request.url).origin;
const { validated, violations } = validateOperations(operations, origin);
if (violations.length > 0) {
throw new ValidationError(violations);
}
const headers = buildSubRequestHeaders(ctx.request.headers);
const results = await Promise.all(
validated.map((op) => runOperation(op, headers, fetchImpl)),
);
const succeeded = results.filter((r) => r.status >= 200 && r.status < 300 && !r.error).length;
return { results, succeeded, failed: results.length - succeeded };
};
}
export const executeBatch = createExecuteBatch();
View on GitHub (pinned to eeab0a219f)
Solutions
- Deduplicate ids (or entire operations) before submitting
- Derive ids from the loop index or a counter when generating operations programmatically
- If two identical calls are genuinely needed, give them distinct ids like 'flights-a' and 'flights-b'
Example fix
// before — template collision when the same route appears twice
ops.map((p) => ({ id: `flights-${p.origin}-${p.destination}`, path: p.path }));
// after — index guarantees uniqueness
ops.map((p, i) => ({ id: `flights-${i}`, path: p.path })); Defensive patterns
Strategy: validation
Validate before calling
function dedupeOperationIds<T extends { id?: string; path: string }>(ops: T[]): T[] {
const seen = new Set<string>();
return ops.map((op, i) => {
const base = (op.id ?? String(i)).trim();
let id = base;
let n = 2;
while (seen.has(id)) id = `${base.slice(0, 60)}-${n++}`;
seen.add(id);
return { ...op, id };
});
} Type guard
function isDuplicateIdViolation(body: unknown): boolean {
const v = (body as { violations?: { field?: string; description?: string }[] })?.violations;
return Array.isArray(v) && v.some((x) => x.field?.startsWith('operations[') && x.description?.includes('duplicate id'));
} Try / catch
try {
await post('/api/batch/v1/execute', { operations });
} catch (e) {
if (e instanceof HttpError && e.status === 400 && isDuplicateIdViolation(e.body)) {
// dedupe ids (see validationCode) and resend — paths themselves are fine
}
} Prevention
- Derive ids from the loop index or a counter when generating operations programmatically
- Remember ids are trimmed before comparison — 'a' and ' a ' collide
- Run a Set-based uniqueness assert in dev builds of your client to catch collisions early
When it happens
Trigger: Copy-pasted operation objects without updating id; template-generated ids that collide (e.g. same origin/destination pair in two ops both ided 'flights'); ids that differ only by surrounding whitespace ('a' vs ' a ') which trim to the same value.
Common situations: Deduplicating operations by path but not by id; building ids from a domain key that repeats across categories; a loop reusing a constant id string.
Related errors
- must be at most ${MAX_OPERATION_ID_LENGTH} characters
- Nested batch requests are not allowed
- must contain between 1 and ${MAX_BATCH_OPERATIONS} operation
- get_intel_timeline requires at least one of domain ("conflic
- INCOMPATIBLE_DELIVERY
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/173c1a57cfad3dc7.
Report an issue: GitHub.