{"record":{"id":"173c1a57cfad3dc7","repo":"koala73/worldmonitor","slug":"duplicate-id-id-results-would-be-ambiguous","errorCode":null,"errorMessage":"duplicate id \"${id}\" — results would be ambiguous","messagePattern":"duplicate id \"(.+?)\" — results would be ambiguous","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/batch/v1/execute-batch.ts","lineNumber":208,"sourceCode":"    // Recursion guard: the gateway forwards the marker untouched, so a batch\n    // arriving with it was issued BY a batch — refuse regardless of the\n    // per-path nested_batch check below.\n    if (ctx.request.headers.has(BATCH_MARKER_HEADER)) {\n      throw new ApiError(400, 'Nested batch requests are not allowed', '');\n    }\n\n    const operations = Array.isArray(req.operations) ? req.operations : [];\n    if (operations.length < 1 || operations.length > MAX_BATCH_OPERATIONS) {\n      throw new ValidationError([{\n        field: 'operations',\n        description: `must contain between 1 and ${MAX_BATCH_OPERATIONS} operations`,\n      }]);\n    }\n\n    const origin = new URL(ctx.request.url).origin;\n    const { validated, violations } = validateOperations(operations, origin);\n    if (violations.length > 0) {\n      throw new ValidationError(violations);\n    }\n\n    const headers = buildSubRequestHeaders(ctx.request.headers);\n    const results = await Promise.all(\n      validated.map((op) => runOperation(op, headers, fetchImpl)),\n    );\n\n    const succeeded = results.filter((r) => r.status >= 200 && r.status < 300 && !r.error).length;\n    return { results, succeeded, failed: results.length - succeeded };\n  };\n}\n\nexport const executeBatch = createExecuteBatch();\n","sourceCodeStart":190,"sourceCodeEnd":222,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/server/worldmonitor/batch/v1/execute-batch.ts#L190-L222","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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'"],"exampleFix":"// before — template collision when the same route appears twice\nops.map((p) => ({ id: `flights-${p.origin}-${p.destination}`, path: p.path }));\n\n// after — index guarantees uniqueness\nops.map((p, i) => ({ id: `flights-${i}`, path: p.path }));","handlingStrategy":"validation","validationCode":"function dedupeOperationIds<T extends { id?: string; path: string }>(ops: T[]): T[] {\n  const seen = new Set<string>();\n  return ops.map((op, i) => {\n    const base = (op.id ?? String(i)).trim();\n    let id = base;\n    let n = 2;\n    while (seen.has(id)) id = `${base.slice(0, 60)}-${n++}`;\n    seen.add(id);\n    return { ...op, id };\n  });\n}","typeGuard":"function isDuplicateIdViolation(body: unknown): boolean {\n  const v = (body as { violations?: { field?: string; description?: string }[] })?.violations;\n  return Array.isArray(v) && v.some((x) => x.field?.startsWith('operations[') && x.description?.includes('duplicate id'));\n}","tryCatchPattern":"try {\n  await post('/api/batch/v1/execute', { operations });\n} catch (e) {\n  if (e instanceof HttpError && e.status === 400 && isDuplicateIdViolation(e.body)) {\n    // dedupe ids (see validationCode) and resend — paths themselves are fine\n  }\n}","preventionTips":["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"],"tags":["batch","validation","duplicate-id","operation-id"],"backgroundTag":"duplicate-key-rejected","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}