{"record":{"id":"abc3b3870b1663d1","repo":"different-ai/openwork","slug":"failed-to-add-workflow-response-status","errorCode":null,"errorMessage":"Failed to add Workflow (${response.status}).","messagePattern":"Failed to add Workflow \\((.+?)\\)\\.","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/plugin-data.tsx","lineNumber":817,"sourceCode":"}\n\nexport function useAttachWorkflowToPlugin(pluginId: string) {\n  const queryClient = useQueryClient();\n  const { runReauthableAction } = useOrgDashboard();\n\n  return useMutation({\n    mutationFn: async (workflowId: string) => {\n      await runReauthableAction(\"attach-workflow-to-plugin\", async () => {\n        const { response, payload } = await requestJson(\n          `/v1/plugins/${encodeURIComponent(pluginId)}/config-objects`,\n          {\n            method: \"POST\",\n            body: JSON.stringify({ configObjectId: workflowId, membershipSource: \"manual\" }),\n          },\n          15000,\n        );\n        if (!response.ok) {\n          throw getRequestError(payload, response, `Failed to add Workflow (${response.status}).`);\n        }\n      });\n      return workflowId;\n    },\n    onSuccess: async () => {\n      await Promise.all([\n        queryClient.invalidateQueries({ queryKey: pluginQueryKeys.detail(pluginId) }),\n        queryClient.invalidateQueries({ queryKey: pluginQueryKeys.list() }),\n        queryClient.invalidateQueries({ queryKey: [\"me\", \"library\"] }),\n      ]);\n    },\n  });\n}\n","sourceCodeStart":799,"sourceCodeEnd":831,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/plugin-data.tsx#L799-L831","documentation":"Thrown by useAttachWorkflowToPlugin in plugin-data.tsx when POSTing a workflow membership to the plugin returns a non-ok response. The body includes configObjectId and membershipSource 'manual'; getRequestError decorates the throw with server payload and status. Indicates the Den API refused to attach the workflow.","triggerScenarios":"POST to the plugin's workflows endpoint with { configObjectId, membershipSource: 'manual' } returns 4xx/5xx: workflow id already attached (409), workflow deleted (404), permission denied (403), or invalid configObjectId format (400).","commonSituations":"Two tabs attaching the same workflow concurrently; attaching a workflow that another admin removed moments ago; selecting a workflow from a different org due to stale cache.","solutions":["Read the status: 409 -> the workflow is already attached, no action needed; 404 -> refresh workflow list; 403 -> check org role.","Refresh plugin detail and workflow lists to clear stale ids, then retry.","Validate the workflowId belongs to the same org before attaching.","For 5xx, retry once after server health check."],"exampleFix":"// before\nawait attachWorkflow(pluginId, workflowId);\n// after: skip if already attached\nconst attached = pluginWorkflowIds.includes(workflowId);\nif (!attached) await attachWorkflow(pluginId, workflowId);","handlingStrategy":"validation","validationCode":"const alreadyAttached = plugin.detail?.workflowIds?.includes(workflowId);\nif (alreadyAttached) return; // nothing to do, avoids 409\nif (!workflowId) throw new Error('Select a workflow before attaching.');","typeGuard":"function isWorkflowId(v: unknown): v is string {\n  return typeof v === 'string' && v.length > 0;\n}","tryCatchPattern":"try {\n  await attachWorkflow(pluginId, workflowId);\n} catch (e) {\n  const msg = e instanceof Error ? e.message : String(e);\n  if (msg.includes('409')) toast('This workflow is already attached.');\n  else if (msg.includes('404')) { await refreshWorkflows(); toast('Workflow no longer exists.'); }\n  else toast(msg);\n}","preventionTips":["Check the plugin's current workflow list before attaching to avoid duplicate 409s.","Invalidate/refresh workflow queries after mutations so ids stay fresh.","Restrict attach UI to org admins.","Log the response status server-side to distinguish conflict vs missing."],"tags":["http","api","workflows","plugin-management"],"backgroundTag":"http-non-ok-response","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}