{"record":{"id":"b73b8674aa548949","repo":"different-ai/openwork","slug":"a-manual-occurrence-needs-a-nonce","errorCode":null,"errorMessage":"A manual occurrence needs a nonce","messagePattern":"A manual occurrence needs a nonce","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/automations/src/contracts.ts","lineNumber":44,"sourceCode":"  for (let index = 0; index < input.length; index += 1) {\n    const code = input.charCodeAt(index)\n    left = Math.imul(left ^ code, 0x01000193)\n    right = Math.imul(right ^ code, 0x85ebca6b)\n  }\n  return `${(left >>> 0).toString(16).padStart(8, \"0\")}${(right >>> 0).toString(16).padStart(8, \"0\")}`\n}\n\nexport interface AutomationOccurrenceIdentityInput {\n  automationId: string\n  scheduledFor: number | null\n  nonce?: string\n}\n\nexport function automationOccurrenceIdentity(input: AutomationOccurrenceIdentityInput): {\n  occurrenceId: string\n  idempotencyKey: string\n} {\n  if (input.scheduledFor === null && !input.nonce) throw new Error(\"A manual occurrence needs a nonce\")\n  const occurrence = input.scheduledFor === null ? `manual:${input.nonce}` : String(input.scheduledFor)\n  const stable = [input.automationId, occurrence]\n    .map(encodeURIComponent).join(\":\")\n  return {\n    occurrenceId: `automation-occurrence:${stable}`,\n    idempotencyKey: `automation:${stable}`,\n  }\n}\n","sourceCodeStart":26,"sourceCodeEnd":53,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/automations/src/contracts.ts#L26-L53","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass a unique nonce, e.g. crypto.randomUUID(), for every manual occurrence","If the run is actually scheduled, supply the real scheduledFor timestamp instead of null","Audit the calling helper (identity/scheduled) to ensure manual paths always populate nonce"],"exampleFix":"// before\nconst id = automationOccurrenceIdentity({ automationId: \"a1\", scheduledFor: null })\n// after\nconst id = automationOccurrenceIdentity({ automationId: \"a1\", scheduledFor: null, nonce: crypto.randomUUID() })","handlingStrategy":"validation","validationCode":"if (input.scheduledFor === null && !input.nonce) {\n  throw new Error(\"Manual occurrence requires a nonce\")\n}\nautomationOccurrenceIdentity(input)","typeGuard":"const isManualWithNonce = (i: AutomationOccurrenceIdentityInput): i is AutomationOccurrenceIdentityInput & { nonce: string } =>\n  i.scheduledFor !== null || (typeof i.nonce === \"string\" && i.nonce.length > 0)","tryCatchPattern":"try {\n  const id = automationOccurrenceIdentity(input)\n} catch (e) {\n  if (e instanceof Error && e.message === \"A manual occurrence needs a nonce\") {\n    return automationOccurrenceIdentity({ ...input, nonce: crypto.randomUUID() })\n  }\n  throw e\n}","preventionTips":["Always generate a nonce (crypto.randomUUID()) at the manual-trigger entry point","Centralize manual-run creation in one helper so nonce generation can't be skipped","Add a unit test asserting manual identity inputs always include a nonce"],"tags":["idempotency","automations","api-misuse"],"backgroundTag":"missing-idempotency-key","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}