{"record":{"id":"5d408d1cdf4da499","repo":"different-ai/openwork","slug":"saved-workflow-manager-context-required","errorCode":"saved_workflow_manager_context_required","errorMessage":"saved_workflow_manager_context_required","messagePattern":"saved_workflow_manager_context_required","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-api/src/workflows.ts","lineNumber":721,"sourceCode":"      }).where(eq(PluginTable.id, plugin.id))\n    }\n\n    const linked = await tx.select({ object: ConfigObjectTable }).from(PluginConfigObjectTable)\n      .innerJoin(ConfigObjectTable, eq(ConfigObjectTable.id, PluginConfigObjectTable.configObjectId))\n      .where(and(\n        eq(PluginConfigObjectTable.pluginId, pluginId),\n        isNull(PluginConfigObjectTable.removedAt),\n        eq(ConfigObjectTable.title, input.workflow.name),\n        inArray(ConfigObjectTable.objectType, [\"script\", \"workflow\"]),\n        eq(ConfigObjectTable.status, \"active\"),\n        isNull(ConfigObjectTable.deletedAt),\n      )).limit(1).for(\"update\")\n    const configObjectId = linked[0]?.object.id ?? createDenTypeId(\"configObject\")\n    if (linked[0]) {\n      // Saving the same name creates a new immutable version of the existing\n      // Workflow. Plugin edit access can widen the audience, but it must never\n      // grant authority to replace another Workflow manager's executable code.\n      if (!input.context) throw new Error(\"saved_workflow_manager_context_required\")\n      await requirePluginArchResourceRole({\n        context: input.context,\n        resourceId: configObjectId,\n        resourceKind: \"config_object\",\n        role: \"manager\",\n      })\n    } else {\n      await tx.insert(ConfigObjectTable).values({\n        id: configObjectId,\n        organizationId,\n        objectType: \"workflow\",\n        sourceMode: \"cloud\",\n        title: input.workflow.name,\n        description: input.workflow.description?.trim() || null,\n        searchText: `${input.workflow.name} ${input.workflow.description ?? \"\"}`.trim(),\n        currentFileName: `${input.workflow.name}.js`,\n        currentFileExtension: \"js\",\n        status: \"active\",","sourceCodeStart":703,"sourceCodeEnd":739,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/workflows.ts#L703-L739","documentation":"Saving a workflow under a name that already has a linked active config object creates a new immutable version of that workflow instead of a new workflow. Because replacing another manager's executable code must never happen implicitly, saveWorkflow requires an actor context (input.context) to check the caller holds the 'manager' role on the existing config object; without any context it throws 'saved_workflow_manager_context_required'.","triggerScenarios":"Calling saveWorkflow without input.context while input.workflow.name matches the title of an existing active script/workflow config object linked to the (resolved) plugin — the overwrite-versioning path at line 717.","commonSituations":"An unauthenticated/system caller re-saving a workflow whose name collides with an existing one; renaming a workflow to a name another manager already uses; background jobs omitting context that worked before only because names were unique.","solutions":["Pass an actor context whose member holds the manager role on the existing config object","Rename the workflow to a non-colliding title so a brand-new config object is created instead","If you should not manage the existing workflow, choose a distinct name rather than overwriting","For programmatic callers, load the PluginArchActorContext for the owning member before saving"],"exampleFix":"// before\nawait saveWorkflow({ organizationId, ownerMemberId, workflow: { name: existingName, ... }, buildTools }) // contextless overwrite\n// after\nawait saveWorkflow({ organizationId, ownerMemberId, workflow: { name: 'my-report-v2', ... }, buildTools, context: actorContextWithManagerRole })","handlingStrategy":"validation","validationCode":"async function collidesWithExistingWorkflow(name: string, pluginId: string) {\n  const rows = await db.select({ id: ConfigObjectTable.id }).from(PluginConfigObjectTable)\n    .innerJoin(ConfigObjectTable, eq(ConfigObjectTable.id, PluginConfigObjectTable.configObjectId))\n    .where(and(\n      eq(PluginConfigObjectTable.pluginId, pluginId),\n      isNull(PluginConfigObjectTable.removedAt),\n      eq(ConfigObjectTable.title, name),\n      inArray(ConfigObjectTable.objectType, ['script', 'workflow']),\n      eq(ConfigObjectTable.status, 'active'),\n      isNull(ConfigObjectTable.deletedAt),\n    )).limit(1)\n  return rows.length > 0 // if true, an actor context with the manager role is required\n}","typeGuard":"null","tryCatchPattern":"try {\n  await saveWorkflow(input)\n} catch (err) {\n  if (err instanceof Error && err.message === 'saved_workflow_manager_context_required') {\n    console.error('Name collides with an existing workflow — supply a manager-role context or pick a new name')\n  }\n}","preventionTips":["Check for title collisions in the UI before submitting a save without context","Always attach an actor context when overwriting an existing workflow by name","Delegate the manager-role check to requirePluginArchResourceRole rather than reimplementing it","Name new workflows uniquely (e.g. include a version suffix) to avoid the overwrite path"],"tags":["authorization","den-api","workflows","context"],"backgroundTag":"missing-authorization-context","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}