{"record":{"id":"170f21a61aab365b","repo":"different-ai/openwork","slug":"saved-workflow-plugin-not-found","errorCode":"saved_workflow_plugin_not_found","errorMessage":"saved_workflow_plugin_not_found","messagePattern":"saved_workflow_plugin_not_found","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-api/src/workflows.ts","lineNumber":678,"sourceCode":"        )).limit(1).for(\"update\")\n      : await tx.select().from(PluginTable).where(and(\n          eq(PluginTable.organizationId, organizationId),\n          eq(PluginTable.createdByOrgMembershipId, ownerMemberId),\n          eq(PluginTable.name, DEFAULT_WORKFLOWS_PLUGIN_NAME),\n          eq(PluginTable.status, \"active\"),\n          isNull(PluginTable.deletedAt),\n        )).limit(1).for(\"update\")\n    const legacyPlugins = requestedPluginId || plugins[0]\n      ? []\n      : await tx.select().from(PluginTable).where(and(\n          eq(PluginTable.organizationId, organizationId),\n          eq(PluginTable.createdByOrgMembershipId, ownerMemberId),\n          inArray(PluginTable.name, LEGACY_WORKFLOW_PLUGIN_NAMES),\n          eq(PluginTable.status, \"active\"),\n          isNull(PluginTable.deletedAt),\n        )).limit(1).for(\"update\")\n    const plugin = plugins[0] ?? legacyPlugins[0]\n    if (requestedPluginId && !plugin) throw new Error(\"saved_workflow_plugin_not_found\")\n    const pluginId = plugin?.id ?? createDenTypeId(\"plugin\")\n    if (!plugin) {\n      await tx.insert(PluginTable).values({\n        id: pluginId,\n        organizationId,\n        name: DEFAULT_WORKFLOWS_PLUGIN_NAME,\n        description: \"Private reusable Workflows.\",\n        status: \"active\",\n        createdByOrgMembershipId: ownerMemberId,\n      })\n      await tx.insert(PluginAccessGrantTable).values({\n        id: createDenTypeId(\"pluginAccessGrant\"),\n        organizationId,\n        pluginId,\n        orgMembershipId: ownerMemberId,\n        teamId: null,\n        orgWide: false,\n        role: \"manager\",","sourceCodeStart":660,"sourceCodeEnd":696,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/workflows.ts#L660-L696","documentation":"When saving under an explicit pluginId, saveWorkflow looks up the plugin row (id, same organization, active, not deleted, locked FOR UPDATE). If requestedPluginId was supplied but no matching active plugin row exists, it throws 'saved_workflow_plugin_not_found'. Legacy-name fallback only applies when no explicit pluginId was requested.","triggerScenarios":"Calling saveWorkflow with workflow.pluginId set to a plugin id that does not exist, belongs to a different organization, has status != 'active', or was soft-deleted (deletedAt set).","commonSituations":"Stale pluginId cached in a client after the plugin was deleted; copying a workflow/pluginId from another org into this one; a race where the plugin is deleted between listing and saving; passing an id from a test environment into production.","solutions":["Verify the pluginId exists, is active, not soft-deleted, and belongs to input.organizationId before saving","Omit workflow.pluginId to save into the caller's default workflows plugin (auto-created if missing)","Refresh the client's plugin list to replace the stale id","Re-create the plugin if it was deleted and use its new id"],"exampleFix":"// before\nconst [plugin] = await db.select().from(PluginTable).where(eq(PluginTable.id, staleId))\nawait saveWorkflow({ workflow: { pluginId: staleId, ... } }) // throws if stale\n// after\nconst [plugin] = await db.select().from(PluginTable).where(and(eq(PluginTable.id, staleId), eq(PluginTable.organizationId, organizationId), eq(PluginTable.status, 'active'), isNull(PluginTable.deletedAt)))\nif (!plugin) throw new Error('re-create or re-select the plugin first')\nawait saveWorkflow({ workflow: { pluginId: plugin.id, ... } })","handlingStrategy":"validation","validationCode":"async function pluginIsSaveable(pluginId: string, organizationId: string) {\n  const [p] = await db.select().from(PluginTable).where(and(\n    eq(PluginTable.id, pluginId),\n    eq(PluginTable.organizationId, organizationId),\n    eq(PluginTable.status, 'active'),\n    isNull(PluginTable.deletedAt),\n  )).limit(1)\n  return p != null\n}","typeGuard":"null","tryCatchPattern":"try {\n  await saveWorkflow(input)\n} catch (err) {\n  if (err instanceof Error && err.message === 'saved_workflow_plugin_not_found') {\n    console.error('pluginId is missing/inactive/deleted or belongs to another org — refresh and reselect')\n  }\n}","preventionTips":["Refresh plugin selections after any delete/deactivate operation","Never carry pluginIds across organizations or environments","Prefer omitting pluginId (default workflows plugin) unless targeting a shared plugin explicitly","Handle stale-id errors by re-fetching the plugin list instead of retrying blindly"],"tags":["not-found","den-api","workflows","plugins"],"backgroundTag":"resource-not-found","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}