different-ai/openwork · error

Failed to update plugin (${response.status}).

Error message

Failed to update plugin (${response.status}).

What it means

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.

Source

Thrown at ee/apps/den-web/app/(den)/dashboard/_components/plugin-data.tsx:764

}

export function useUpdatePlugin() {
  const queryClient = useQueryClient();
  const { runReauthableAction } = useOrgDashboard();

  return useMutation({
    mutationFn: async (input: { pluginId: string; name: string; description: string | null }) => {
      await runReauthableAction("update-plugin", async () => {
        const { response, payload } = await requestJson(
          `/v1/plugins/${encodeURIComponent(input.pluginId)}`,
          {
            method: "PATCH",
            body: JSON.stringify({ name: input.name, description: input.description }),
          },
          15000,
        );
        if (!response.ok) {
          throw getRequestError(payload, response, `Failed to update plugin (${response.status}).`);
        }
      });
      return input.pluginId;
    },
    onSuccess: (pluginId) => {
      queryClient.invalidateQueries({ queryKey: pluginQueryKeys.detail(pluginId) });
      queryClient.invalidateQueries({ queryKey: pluginQueryKeys.list() });
    },
  });
}

export function useArchivePlugin() {
  const queryClient = useQueryClient();
  const { runReauthableAction } = useOrgDashboard();

  return useMutation({
    mutationFn: async (pluginId: string) => {
      await runReauthableAction("archive-plugin", async () => {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Read the status: 400 → trim/validate name and description against current limits; 404 → invalidate pluginQueryKeys.all, the plugin is gone.
  2. On 401/403, re-authenticate or verify plugin-edit permissions (branch on isReauthRequiredError).
  3. Invalidate pluginQueryKeys.detail(pluginId) and refetch before retrying.
  4. On 5xx, retry after server recovery; the update is idempotent since PATCH sends full name/description.

Example fix

// before
if (!response.ok) {
  throw getRequestError(payload, response, `Failed to update plugin (${response.status}).`);
}
// after
const name = input.name.trim();
if (!name) throw new Error("Plugin name cannot be empty.");
const patched = await requestJson(pluginUrl, { method: "PATCH", body: JSON.stringify({ name, description: input.description }) }, 15000);
if (!patched.response.ok) {
  throw getRequestError(patched.payload, patched.response, `Failed to update plugin (${patched.response.status}).`);
}
Defensive patterns

Strategy: validation

Validate before calling

const name = input.name.trim();
if (!name) throw new Error("Plugin name is required.");
if (name.length > 100) throw new Error("Plugin name must be 100 characters or fewer.");
if (input.description && input.description.length > 500) throw new Error("Description must be 500 characters or fewer.");

Type guard

function isPluginUpdateInput(value: unknown): value is { pluginId: string; name: string; description: string | null } {
  if (typeof value !== "object" || value === null) return false;
  const v = value as { pluginId?: unknown; name?: unknown; description?: unknown };
  return typeof v.pluginId === "string" && v.pluginId.length > 0
    && typeof v.name === "string" && v.name.trim().length > 0
    && (v.description === null || typeof v.description === "string");
}

Try / catch

try {
  await updatePlugin.mutateAsync({ pluginId, name, description });
} catch (error) {
  if (isReauthRequiredError(error)) return startReauth();
  if (error.message.includes("(404)")) {
    await queryClient.invalidateQueries({ queryKey: pluginQueryKeys.all });
    return toast.error("This plugin no longer exists.");
  }
  toast.error(error.message);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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