{"record":{"id":"56306078081e3fda","repo":"different-ai/openwork","slug":"failed-to-update-plugin-response-status","errorCode":null,"errorMessage":"Failed to update plugin (${response.status}).","messagePattern":"Failed to update plugin \\((.+?)\\)\\.","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/plugin-data.tsx","lineNumber":764,"sourceCode":"}\n\nexport function useUpdatePlugin() {\n  const queryClient = useQueryClient();\n  const { runReauthableAction } = useOrgDashboard();\n\n  return useMutation({\n    mutationFn: async (input: { pluginId: string; name: string; description: string | null }) => {\n      await runReauthableAction(\"update-plugin\", async () => {\n        const { response, payload } = await requestJson(\n          `/v1/plugins/${encodeURIComponent(input.pluginId)}`,\n          {\n            method: \"PATCH\",\n            body: JSON.stringify({ name: input.name, description: input.description }),\n          },\n          15000,\n        );\n        if (!response.ok) {\n          throw getRequestError(payload, response, `Failed to update plugin (${response.status}).`);\n        }\n      });\n      return input.pluginId;\n    },\n    onSuccess: (pluginId) => {\n      queryClient.invalidateQueries({ queryKey: pluginQueryKeys.detail(pluginId) });\n      queryClient.invalidateQueries({ queryKey: pluginQueryKeys.list() });\n    },\n  });\n}\n\nexport function useArchivePlugin() {\n  const queryClient = useQueryClient();\n  const { runReauthableAction } = useOrgDashboard();\n\n  return useMutation({\n    mutationFn: async (pluginId: string) => {\n      await runReauthableAction(\"archive-plugin\", async () => {","sourceCodeStart":746,"sourceCodeEnd":782,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/plugin-data.tsx#L746-L782","documentation":"Thrown by useUpdatePlugin's updatePlugin mutation when the PATCH to the plugin endpoint returns non-ok. The fallback message includes the HTTP status; a server-provided error message in the payload takes precedence. A 403 'reauth' payload is raised as ReauthRequiredError.","triggerScenarios":"Patching a plugin with a rejected name/description (empty, too long, invalid characters — 400), plugin deleted or unpublished by another admin (404), missing edit permission (403), expired token (401), or server error during update.","commonSituations":"Two admins editing the same plugin where one deletes it; name validation rules tightened in a newer API version; session expiry while the edit dialog was open.","solutions":["Read the status: 400 → trim/validate name and description against current limits; 404 → invalidate pluginQueryKeys.all, the plugin is gone.","On 401/403, re-authenticate or verify plugin-edit permissions (branch on isReauthRequiredError).","Invalidate pluginQueryKeys.detail(pluginId) and refetch before retrying.","On 5xx, retry after server recovery; the update is idempotent since PATCH sends full name/description."],"exampleFix":"// before\nif (!response.ok) {\n  throw getRequestError(payload, response, `Failed to update plugin (${response.status}).`);\n}\n// after\nconst name = input.name.trim();\nif (!name) throw new Error(\"Plugin name cannot be empty.\");\nconst patched = await requestJson(pluginUrl, { method: \"PATCH\", body: JSON.stringify({ name, description: input.description }) }, 15000);\nif (!patched.response.ok) {\n  throw getRequestError(patched.payload, patched.response, `Failed to update plugin (${patched.response.status}).`);\n}","handlingStrategy":"validation","validationCode":"const name = input.name.trim();\nif (!name) throw new Error(\"Plugin name is required.\");\nif (name.length > 100) throw new Error(\"Plugin name must be 100 characters or fewer.\");\nif (input.description && input.description.length > 500) throw new Error(\"Description must be 500 characters or fewer.\");","typeGuard":"function isPluginUpdateInput(value: unknown): value is { pluginId: string; name: string; description: string | null } {\n  if (typeof value !== \"object\" || value === null) return false;\n  const v = value as { pluginId?: unknown; name?: unknown; description?: unknown };\n  return typeof v.pluginId === \"string\" && v.pluginId.length > 0\n    && typeof v.name === \"string\" && v.name.trim().length > 0\n    && (v.description === null || typeof v.description === \"string\");\n}","tryCatchPattern":"try {\n  await updatePlugin.mutateAsync({ pluginId, name, description });\n} catch (error) {\n  if (isReauthRequiredError(error)) return startReauth();\n  if (error.message.includes(\"(404)\")) {\n    await queryClient.invalidateQueries({ queryKey: pluginQueryKeys.all });\n    return toast.error(\"This plugin no longer exists.\");\n  }\n  toast.error(error.message);\n}","preventionTips":["Trim and validate name/description client-side before PATCHing.","Refetch the plugin before showing an edit form that may be stale.","Handle 404 as concurrent deletion and invalidate the plugin list.","Invalidate pluginQueryKeys.detail(pluginId) after every update attempt."],"tags":["http","api","plugin","react-query","mutation"],"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"}