{"record":{"id":"aea3528b97e06135","repo":"different-ai/openwork","slug":"automation-engine-event-idempotency-key-was-repeat","errorCode":null,"errorMessage":"Automation engine event idempotency key was repeated","messagePattern":"Automation engine event idempotency key was repeated","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/automations/src/engine.ts","lineNumber":234,"sourceCode":"  if (!Number.isSafeInteger(afterSequence) || afterSequence < 0) {\n    throw new Error(\"Automation engine event cursor must be a non-negative integer\")\n  }\n  let cursor = afterSequence\n  const eventKeys = new Set<string>()\n  return {\n    get cursor() {\n      return cursor\n    },\n    accept(rawEvent) {\n      const event = automationEngineEventSchema.parse(rawEvent)\n      if (event.executionId !== receipt.executionId || event.runId !== receipt.runId) {\n        throw new Error(\"Automation engine event receipt mismatch\")\n      }\n      if (event.sequence !== cursor + 1) {\n        throw new Error(\"Automation engine event sequence is not contiguous\")\n      }\n      if (eventKeys.has(event.idempotencyKey)) {\n        throw new Error(\"Automation engine event idempotency key was repeated\")\n      }\n      eventKeys.add(event.idempotencyKey)\n      cursor = event.sequence\n    },\n  }\n}\n","sourceCodeStart":216,"sourceCodeEnd":241,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/automations/src/engine.ts#L216-L241","documentation":"accept() tracks every event's idempotencyKey and rejects repeats, guaranteeing that replay of the event stream never double-counts an effect. A duplicate key means the same event (same logical effect) was delivered twice under the same key rather than being deduplicated upstream.","triggerScenarios":"Calling accept() twice with events carrying the same idempotencyKey — e.g. the producer re-emitted an event without a new key, or the consumer re-fed an already-accepted event (sequence check passed because cursor advanced elsewhere).","commonSituations":"At-least-once delivery from a queue replaying messages; a producer bug reusing keys across retries; manually replaying an event log slice that overlaps a previously consumed range.","solutions":["Deduplicate events by idempotencyKey in the consumer before calling accept()","Ensure the producer generates a fresh idempotencyKey per logical event, even on retries","Track consumed keys (or cursor checkpoints) externally so overlapping replays skip already-accepted events"],"exampleFix":"// before\nfor (const e of events) validator.accept(e)\n// after\nconst seen = new Set()\nfor (const e of events) { if (seen.has(e.idempotencyKey)) continue; seen.add(e.idempotencyKey); validator.accept(e) }","handlingStrategy":"validation","validationCode":"const seen = new Set<string>()\nfor (const e of rawEvents) {\n  if (seen.has(e.idempotencyKey)) continue\n  seen.add(e.idempotencyKey)\n  validator.accept(e)\n}","typeGuard":"const isUniqueKey = (e: AutomationEngineEvent, seen: Set<string>): boolean => !seen.has(e.idempotencyKey)","tryCatchPattern":"try {\n  validator.accept(event)\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"idempotency key was repeated\")) {\n    logger.warn({ key: event.idempotencyKey }, \"duplicate engine event skipped\")\n  } else throw e\n}","preventionTips":["Generate a fresh idempotencyKey per logical event on the producer, stable across delivery retries","Deduplicate by key at the consumer boundary before acceptance","Record consumed keys/checkpoints so overlapping replays skip already-accepted events"],"tags":["idempotency","event-stream","duplicate"],"backgroundTag":"duplicate-event-delivery","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}