different-ai/openwork · error

Automation engine event receipt mismatch

Error message

Automation engine event receipt mismatch

What it means

The sequence validator is bound to a specific admission receipt (executionId + runId). Every event fed to accept() must belong to that same execution/run. If the event's executionId or runId differs from the receipt's, the event stream and the validated admission are out of sync, so the event is rejected.

Source

Thrown at packages/automations/src/engine.ts:228

/** Validates events before Den persists them and advances its durable cursor. */
export function createAutomationEngineEventSequenceValidator(
  rawReceipt: AutomationEngineAdmissionReceipt,
  afterSequence = 0,
): AutomationEngineEventSequenceValidator {
  const receipt = automationEngineAdmissionReceiptSchema.parse(rawReceipt)
  if (!Number.isSafeInteger(afterSequence) || afterSequence < 0) {
    throw new Error("Automation engine event cursor must be a non-negative integer")
  }
  let cursor = afterSequence
  const eventKeys = new Set<string>()
  return {
    get cursor() {
      return cursor
    },
    accept(rawEvent) {
      const event = automationEngineEventSchema.parse(rawEvent)
      if (event.executionId !== receipt.executionId || event.runId !== receipt.runId) {
        throw new Error("Automation engine event receipt mismatch")
      }
      if (event.sequence !== cursor + 1) {
        throw new Error("Automation engine event sequence is not contiguous")
      }
      if (eventKeys.has(event.idempotencyKey)) {
        throw new Error("Automation engine event idempotency key was repeated")
      }
      eventKeys.add(event.idempotencyKey)
      cursor = event.sequence
    },
  }
}

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Create a fresh validator per execution/run, matching the receipt you obtained from admission
  2. Filter the event stream by the receipt's executionId and runId before calling accept()
  3. If the run was re-admitted, re-fetch the new receipt and rebuild the validator

Example fix

// before
validator.accept(eventFromOtherExecution)
// after
if (event.executionId === receipt.executionId && event.runId === receipt.runId) validator.accept(event)
Defensive patterns

Strategy: type-guard

Validate before calling

const belongsToReceipt = (event: AutomationEngineEvent, receipt: AutomationEngineAdmissionReceipt): boolean =>
  event.executionId === receipt.executionId && event.runId === receipt.runId
// filter stream before accepting: events.filter(e => belongsToReceipt(e, receipt)).forEach(e => validator.accept(e))

Type guard

const isEventForReceipt = (e: AutomationEngineEvent, r: AutomationEngineAdmissionReceipt): boolean =>
  e.executionId === r.executionId && e.runId === r.runId

Try / catch

try {
  validator.accept(event)
} catch (e) {
  if (e instanceof Error && e.message === "Automation engine event receipt mismatch") {
    // rebuild validator from the receipt matching this event's execution/run
    validator = createAutomationEngineEventSequenceValidator(receiptFor(event), event.sequence - 1)
  } else throw e
}

Prevention

When it happens

Trigger: Calling validator.accept(event) with an event produced by a different execution or run than the receipt passed to createAutomationEngineEventSequenceValidator — e.g. mixing event streams, replaying events after a new run started, or a stale validator reused across runs.

Common situations: Caching a validator instance and feeding it a resumed run's events; two concurrent executions writing to a shared event log consumed by one validator; retrying with a new executionId but the old receipt.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/bc7efad251acd323. Report an issue: GitHub.