different-ai/openwork · error

Failed to load plugins (${response.status}).

Error message

Failed to load plugins (${response.status}).

What it means

usePlugins queries GET /v1/plugins?status=active&limit=100 to list active plugins for the org. A non-2xx response throws this error via getErrorMessage with a status-annotated fallback. Because it backs the plugins list hook, the whole dashboard plugin section fails when it throws.

Source

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

    workflows,
    skills,
    slug: slugifyPluginName(name),
    source: marketplaces[0]
      ? { type: "marketplace", marketplace: marketplaces[0].name }
      : { type: "github", repo: "Connected repository" },
    status: asString(pluginItem.status) === "archived" ? "archived" : "active",
    updatedAt: asString(pluginItem.updatedAt) ?? new Date().toISOString(),
    version: null,
  } satisfies DenPlugin;
}

export function usePlugins() {
  return useQuery({
    queryKey: pluginQueryKeys.list(),
    queryFn: async () => {
      const { response, payload } = await requestJson("/v1/plugins?status=active&limit=100", { method: "GET" }, 20000);
      if (!response.ok) {
        throw new Error(getErrorMessage(payload, `Failed to load plugins (${response.status}).`));
      }

      const items = isRecord(payload) && Array.isArray(payload.items) ? payload.items : [];
      const pluginIds = items.flatMap((entry) => {
        const id = isRecord(entry) ? asString(entry.id) : null;
        return id ? [id] : [];
      });

      const plugins = await Promise.all(pluginIds.map((id) => fetchResolvedPlugin(id)));
      return plugins.filter((plugin): plugin is DenPlugin => Boolean(plugin));
    },
  });
}

export function usePlugin(id: string) {
  return useQuery({
    queryKey: pluginQueryKeys.detail(id),
    queryFn: async () => fetchResolvedPlugin(id),

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Sign in / refresh the Den session to clear 401.
  2. Confirm the current user's org has the plugins feature and list permission for 403.
  3. If 400, align den-web and Den server versions (query-string contract mismatch) or drop the unsupported params.
  4. Check Den server health/logs for 5xx and retry once the server recovers; consider a TanStack Query retry for transient statuses.

Example fix

// before: no retry, single throw on any non-ok
const { response, payload } = await requestJson("/v1/plugins?status=active&limit=100", { method: "GET" }, 20000);
if (!response.ok) {
  throw new Error(getErrorMessage(payload, `Failed to load plugins (${response.status}).`));
}
// after: retry transient failures
return useQuery({
  queryKey: pluginQueryKeys.list(),
  retry: (failureCount, error) => failureCount < 2,
  queryFn: async () => {
    const { response, payload } = await requestJson("/v1/plugins?status=active&limit=100", { method: "GET" }, 20000);
    if (!response.ok) throw new Error(getErrorMessage(payload, `Failed to load plugins (${response.status}).`));
    ...
  },
});
Defensive patterns

Strategy: retry

Validate before calling

const sessionOk = await ensureSession();
if (!sessionOk) { await redirectToSignIn(); } // avoids guaranteed 401 on the list call

Type guard

function isPluginListPayload(p: unknown): p is { items: Array<{ id: string }> } {
  if (typeof p !== "object" || p === null || !Array.isArray((p as { items?: unknown }).items)) return false;
  return (p as { items: unknown[] }).items.every((e) => isRecord(e) && typeof e.id === "string");
}

Try / catch

const { isPending, isError, error, refetch } = usePlugins();
if (isError) {
  const transient = /\((5\d\d|429)\)/.test(error.message);
  if (transient) retryWithBackoff(refetch);
  else if (error.message.includes("(401)")) redirectToSignIn();
  else renderEmptyState(error.message);
}

Prevention

When it happens

Trigger: The list endpoint returns 401 (no/Expired Den session), 403 (user lacks org plugin-list permission), 400 (server rejects the query params after an API change), or 5xx (Den server/DB error). It also surfaces if requestJson fails at the network layer with an error status.

Common situations: Fresh den-web deployment where the user hasn't signed in; org without the plugins feature enabled; API contract change (e.g. status=active parameter renamed) on a self-hosted server older than den-web; server maintenance window causing 503.

Related errors


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