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
- Sign in / refresh the Den session to clear 401.
- Confirm the current user's org has the plugins feature and list permission for 403.
- If 400, align den-web and Den server versions (query-string contract mismatch) or drop the unsupported params.
- 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
- Wrap the plugins list in an error boundary + skeleton so the dashboard survives a failed query.
- Enable TanStack Query retries limited to transient statuses and set a staleTime to reduce refetch storms.
- Verify the status=active&limit=100 query contract against the deployed Den server version.
- Keep the user's session fresh (token refresh) before org-level list requests.
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
- Failed to load plugin access (${response.status}).
- Failed to load plugin (${pluginResult.response.status}).
- Failed to load plugin contents (${membershipsResult.response
- This install link returned incomplete setup details.
- Failed to load organizations.
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/30ec9bba0eecf5a6.
Report an issue: GitHub.