{"record":{"id":"c6729b51e63a69b1","repo":"different-ai/openwork","slug":"automation-engine-event-cursor-must-be-a-non-negat","errorCode":null,"errorMessage":"Automation engine event cursor must be a non-negative integer","messagePattern":"Automation engine event cursor must be a non-negative integer","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/automations/src/engine.ts","lineNumber":217,"sourceCode":"  ): Promise<AutomationEngineReadResult | null>\n  cancel(\n    receipt: AutomationEngineAdmissionReceipt,\n  ): Promise<AutomationEngineCancellationResult>\n}\n\nexport interface AutomationEngineEventSequenceValidator {\n  readonly cursor: number\n  accept(event: AutomationEngineEvent): void\n}\n\n/** Validates events before Den persists them and advances its durable cursor. */\nexport function createAutomationEngineEventSequenceValidator(\n  rawReceipt: AutomationEngineAdmissionReceipt,\n  afterSequence = 0,\n): AutomationEngineEventSequenceValidator {\n  const receipt = automationEngineAdmissionReceiptSchema.parse(rawReceipt)\n  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      }","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/automations/src/engine.ts#L199-L235","documentation":"createAutomationEngineEventSequenceValidator replays engine events starting from a cursor (afterSequence). The cursor must be a non-negative safe integer because it is compared against event.sequence to enforce contiguity. Passing a negative, fractional, or non-integer value (including NaN, Infinity, or a string parsed from persistence) is rejected up front.","triggerScenarios":"Calling createAutomationEngineEventSequenceValidator(receipt, afterSequence) where afterSequence is negative, not a safe integer (e.g. 1.5, NaN, Infinity), or was loaded from storage as a string/undefined-coerced value.","commonSituations":"Persisting the cursor in a JSON store and reading it back as a string; subtracting with undefined fields; a stale checkpoint row holding -1 as a sentinel for 'no cursor'.","solutions":["Default the cursor to 0 when no checkpoint exists instead of using -1 or undefined","Coerce persisted values with Number() and validate Number.isSafeInteger(v) && v >= 0 before calling","Pass no second argument to restart validation from the beginning (default 0)"],"exampleFix":"// before\nconst v = createAutomationEngineEventSequenceValidator(receipt, checkpoint.cursor) // -1\n// after\nconst start = Number.isSafeInteger(checkpoint?.cursor) && checkpoint.cursor >= 0 ? checkpoint.cursor : 0\nconst v = createAutomationEngineEventSequenceValidator(receipt, start)","handlingStrategy":"validation","validationCode":"function safeCursor(v: unknown): number {\n  const n = typeof v === \"string\" ? Number(v) : v\n  return typeof n === \"number\" && Number.isSafeInteger(n) && n >= 0 ? n : 0\n}\ncreateAutomationEngineEventSequenceValidator(receipt, safeCursor(checkpoint?.cursor))","typeGuard":"const isValidCursor = (v: unknown): v is number =>\n  typeof v === \"number\" && Number.isSafeInteger(v) && v >= 0","tryCatchPattern":"try {\n  validator = createAutomationEngineEventSequenceValidator(receipt, cursor)\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"non-negative integer\")) {\n    validator = createAutomationEngineEventSequenceValidator(receipt, 0)\n  } else throw e\n}","preventionTips":["Never use -1 as a 'no checkpoint' sentinel; use null/undefined and default to 0","Validate every persisted cursor with Number.isSafeInteger before resuming","Type the checkpoint field as number at the persistence boundary and coerce strings there"],"tags":["validation","cursor","automations"],"backgroundTag":"invalid-cursor-value","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}