different-ai/openwork · error

workflow_recent_receipt_required

workflow_recent_receipt_required

Error message

workflow_recent_receipt_required

What it means

Saving a workflow requires proof that the exact code being saved has actually run successfully recently in this organization by this member. saveWorkflow queries WorkflowRunTable for a succeeded run with a matching code digest finished within RECENT_RUN_WINDOW_MS; if no such receipt exists it throws 'workflow_recent_receipt_required'. This prevents saving untested or never-executed code as a reusable workflow.

Solutions

  1. Execute the workflow code successfully once before saving, with the same member and organization
  2. Confirm the most recent succeeded run is within the recent-run window; re-run if stale
  3. Make sure the code string is byte-identical to what ran (the digest must match — no whitespace-only edits between run and save)
  4. Check that the run was recorded under the same ownerMemberId, not another membership

Example fix

// before
const result = await runCodemode(code) // run failed
await saveWorkflow({ workflow: { code, ... } }) // throws workflow_recent_receipt_required
// after
const result = await runCodemode(code)
if (!result.ok) throw new Error('fix code and re-run before saving')
await saveWorkflow({ workflow: { code, ... } })
Defensive patterns

Strategy: validation

Validate before calling

async function hasRecentRunReceipt(code: string, organizationId: string, member: string) {
  const rows = await db.select().from(WorkflowRunTable).where(and(
    eq(WorkflowRunTable.organization_id, organizationId),
    eq(WorkflowRunTable.org_membership_id, member),
    eq(WorkflowRunTable.code_digest, codemodeCodeDigest(code)),
    eq(WorkflowRunTable.status, 'succeeded'),
    gt(WorkflowRunTable.finished_at, new Date(Date.now() - RECENT_RUN_WINDOW_MS)),
  )).limit(1)
  return rows.length > 0
}

Type guard

null

Try / catch

try {
  await saveWorkflow(input)
} catch (err) {
  if (err instanceof Error && err.message === 'workflow_recent_receipt_required') {
    // surface 'run the workflow successfully, then save' to the user
  }
}

Prevention

When it happens

Trigger: Calling saveWorkflow with workflow.code whose codemodeCodeDigest has no succeeded WorkflowRun row for the same organization_id, org_membership_id, finished within the recent-run window (e.g. the run failed, ran under a different member/org, or is older than the window).

Common situations: Editing code and saving before re-running it in the UI; the test run happened more than RECENT_RUN_WINDOW_MS ago; the run executed under a different member account; a failed or still-running run is the only record.

Related errors


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

Appendix: source

Thrown at ee/apps/den-api/src/workflows.ts:620

    ) {
      throw new Error("saved_workflow_plugin_context_required")
    }
    await requirePluginArchResourceRole({
      context: input.context,
      resourceId: requestedPluginId,
      resourceKind: "plugin",
      role: "editor",
    })
  }
  const receipts = await db.select().from(WorkflowRunTable).where(and(
    eq(WorkflowRunTable.organization_id, organizationId),
    eq(WorkflowRunTable.org_membership_id, ownerMemberId),
    eq(WorkflowRunTable.code_digest, codemodeCodeDigest(input.workflow.code)),
    eq(WorkflowRunTable.status, "succeeded"),
    gt(WorkflowRunTable.finished_at, new Date(Date.now() - RECENT_RUN_WINDOW_MS)),
  )).orderBy(desc(WorkflowRunTable.finished_at)).limit(1)
  const receipt = receipts[0]
  if (!receipt) throw new Error("workflow_recent_receipt_required")

  const built = await input.buildTools()
  const manifestByPath = new Map(built.manifest.flatMap((entry) => [
    [entry.scriptPath, entry] as const,
    [entry.scriptPath.replace(/^tools\./, ""), entry] as const,
  ]))
  const requiredCapabilities: Array<{ capabilityName: string; scriptPath: string }> = []
  for (const call of parseCodemodeToolCalls(receipt.tool_calls)) {
    const resolved = manifestByPath.get(call.name)
    if (!resolved) throw new Error(`workflow_capability_unavailable:${call.name}`)
    if (resolved.readOnly !== true) throw new Error(`workflow_requires_read_only_capabilities:${call.name}`)
    if (!requiredCapabilities.some((entry) => entry.scriptPath === resolved.scriptPath)) {
      requiredCapabilities.push({
        capabilityName: resolved.capabilityName,
        scriptPath: resolved.scriptPath,
      })
    }
  }

View on GitHub (pinned to 2b7df46e8a)