different-ai/openwork · error

A manual occurrence needs a nonce

Error message

A manual occurrence needs a nonce

What it means

automationOccurrenceIdentity builds a stable occurrenceId/idempotencyKey pair from an automationId plus either a scheduled timestamp or a manual nonce. Manual occurrences (scheduledFor === null) have no natural timestamp to key on, so a caller-supplied nonce is required to deduplicate retries. If scheduledFor is null and no nonce is provided, the identity would collide across unrelated manual runs, so the function refuses to fabricate one.

Source

Thrown at packages/automations/src/contracts.ts:44

  for (let index = 0; index < input.length; index += 1) {
    const code = input.charCodeAt(index)
    left = Math.imul(left ^ code, 0x01000193)
    right = Math.imul(right ^ code, 0x85ebca6b)
  }
  return `${(left >>> 0).toString(16).padStart(8, "0")}${(right >>> 0).toString(16).padStart(8, "0")}`
}

export interface AutomationOccurrenceIdentityInput {
  automationId: string
  scheduledFor: number | null
  nonce?: string
}

export function automationOccurrenceIdentity(input: AutomationOccurrenceIdentityInput): {
  occurrenceId: string
  idempotencyKey: string
} {
  if (input.scheduledFor === null && !input.nonce) throw new Error("A manual occurrence needs a nonce")
  const occurrence = input.scheduledFor === null ? `manual:${input.nonce}` : String(input.scheduledFor)
  const stable = [input.automationId, occurrence]
    .map(encodeURIComponent).join(":")
  return {
    occurrenceId: `automation-occurrence:${stable}`,
    idempotencyKey: `automation:${stable}`,
  }
}

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Pass a unique nonce, e.g. crypto.randomUUID(), for every manual occurrence
  2. If the run is actually scheduled, supply the real scheduledFor timestamp instead of null
  3. Audit the calling helper (identity/scheduled) to ensure manual paths always populate nonce

Example fix

// before
const id = automationOccurrenceIdentity({ automationId: "a1", scheduledFor: null })
// after
const id = automationOccurrenceIdentity({ automationId: "a1", scheduledFor: null, nonce: crypto.randomUUID() })
Defensive patterns

Strategy: validation

Validate before calling

if (input.scheduledFor === null && !input.nonce) {
  throw new Error("Manual occurrence requires a nonce")
}
automationOccurrenceIdentity(input)

Type guard

const isManualWithNonce = (i: AutomationOccurrenceIdentityInput): i is AutomationOccurrenceIdentityInput & { nonce: string } =>
  i.scheduledFor !== null || (typeof i.nonce === "string" && i.nonce.length > 0)

Try / catch

try {
  const id = automationOccurrenceIdentity(input)
} catch (e) {
  if (e instanceof Error && e.message === "A manual occurrence needs a nonce") {
    return automationOccurrenceIdentity({ ...input, nonce: crypto.randomUUID() })
  }
  throw e
}

Prevention

When it happens

Trigger: Calling automationOccurrenceIdentity({ automationId, scheduledFor: null }) with nonce undefined/empty string. Typically from identity() or a scheduled() helper that resolves a manual trigger without generating a nonce.

Common situations: Manually triggering an automation run via a button/CLI where the caller forgot to generate a nonce (e.g. crypto.randomUUID()); a refactor that changed scheduledFor to null for 'run now' semantics without adding nonce plumbing.

Related errors


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