different-ai/openwork · error

saved_workflow_plugin_context_required

saved_workflow_plugin_context_required

Error message

saved_workflow_plugin_context_required

What it means

saveWorkflow in the Den API enforces that when a workflow is saved under an explicit pluginId, the caller must supply a PluginArchActorContext whose organization and current member exactly match the organizationId and ownerMemberId arguments. The library throws 'saved_workflow_plugin_context_required' when input.context is missing or its organization/member identity diverges, because plugin-level authorization (requirePluginArchResourceRole with role 'editor') cannot be performed without a matching actor context.

Source

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

export async function saveWorkflow(input: {
  organizationId: string
  ownerMemberId: string
  workflow: SaveWorkflowInput
  buildTools: () => Promise<BuiltCodemodeTools>
  context?: PluginArchActorContext
}): Promise<{ pluginId: string; configObjectId: string; configObjectVersionId: string }> {
  const organizationId = normalizeDenTypeId("organization", input.organizationId)
  const ownerMemberId = normalizeDenTypeId("member", input.ownerMemberId)
  const requestedPluginId = input.workflow.pluginId
    ? normalizeDenTypeId("plugin", input.workflow.pluginId)
    : null
  if (requestedPluginId) {
    if (
      !input.context
      || input.context.organizationContext.organization.id !== organizationId
      || input.context.organizationContext.currentMember.id !== ownerMemberId
    ) {
      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")

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Always pass a PluginArchActorContext when workflow.pluginId is provided
  2. Ensure input.context.organizationContext.organization.id equals the organizationId argument
  3. Ensure input.context.organizationContext.currentMember.id equals the ownerMemberId argument
  4. If saving to the member's default workflows plugin, omit workflow.pluginId so no context is required

Example fix

// before
await saveWorkflow({ organizationId, ownerMemberId, workflow: { pluginId: somePluginId, ... }, buildTools })
// after
await saveWorkflow({ organizationId, ownerMemberId, workflow: { pluginId: somePluginId, ... }, buildTools, context: actorContext // must match organizationId & ownerMemberId
})
Defensive patterns

Strategy: validation

Validate before calling

function canSaveToPlugin(input) {
  return !input.workflow.pluginId || Boolean(
    input.context
    && input.context.organizationContext.organization.id === input.organizationId
    && input.context.organizationContext.currentMember.id === input.ownerMemberId
  )
}

Type guard

function hasMatchingActorContext(input: Parameters<typeof saveWorkflow>[0]): input is typeof input & { context: PluginArchActorContext } {
  return input.context != null
    && input.context.organizationContext.organization.id === input.organizationId
    && input.context.organizationContext.currentMember.id === input.ownerMemberId
}

Try / catch

try {
  await saveWorkflow(input)
} catch (err) {
  if (err instanceof Error && err.message === 'saved_workflow_plugin_context_required') {
    throw new Error('Provide an actor context matching organizationId/ownerMemberId when pluginId is set')
  }
  throw err
}

Prevention

When it happens

Trigger: Calling saveWorkflow with workflow.pluginId set while (a) input.context is undefined, (b) input.context.organizationContext.organization.id differs from input.organizationId, or (c) input.context.organizationContext.currentMember.id differs from input.ownerMemberId.

Common situations: Server-side/cron callers saving workflows on behalf of a member without loading the actor context; passing a context from a different organization after an org switch; reusing a stale member id after the owner membership changed; internal scripts that omit context for the fast path but also set a pluginId.

Related errors


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