different-ai/openwork · error
Failed to load plugin (${pluginResult.response.status}).
Error message
Failed to load plugin (${pluginResult.response.status}). What it means
fetchResolvedPlugin issues two parallel GETs (/v1/plugins/:id and /v1/plugins/:id/resolved). If the plugin-detail request returns a non-2xx status it throws this error, using the server's error message from the payload when available. Callers (usePlugin, plugins queries) surface it via TanStack Query.
Source
Thrown at ee/apps/den-web/app/(den)/dashboard/_components/plugin-data.tsx:623
case "SessionStart":
case "SessionEnd":
case "UserPromptSubmit":
case "Notification":
case "Stop":
return value;
default:
return "Notification";
}
}
async function fetchResolvedPlugin(id: string): Promise<DenPlugin | null> {
const [pluginResult, membershipsResult] = await Promise.all([
requestJson(`/v1/plugins/${encodeURIComponent(id)}`, { method: "GET" }, 15000),
requestJson(`/v1/plugins/${encodeURIComponent(id)}/resolved`, { method: "GET" }, 15000),
]);
if (!pluginResult.response.ok) {
throw new Error(getErrorMessage(pluginResult.payload, `Failed to load plugin (${pluginResult.response.status}).`));
}
if (!membershipsResult.response.ok) {
throw new Error(getErrorMessage(membershipsResult.payload, `Failed to load plugin contents (${membershipsResult.response.status}).`));
}
const pluginItem = isRecord(pluginResult.payload) && isRecord(pluginResult.payload.item) ? pluginResult.payload.item : null;
if (!pluginItem) {
return null;
}
const pluginId = asString(pluginItem.id);
const name = asString(pluginItem.name);
if (!pluginId || !name) {
return null;
}
const membershipItems = isRecord(membershipsResult.payload) && Array.isArray(membershipsResult.payload.items)
? membershipsResult.payload.items.map(parseMembershipConfigObject).filter((value): value is NonNullable<typeof value> => Boolean(value))View on GitHub (pinned to 2b7df46e8a)
Solutions
- Re-authenticate if status is 401/403; verify the session cookie is present in the failing request.
- Confirm the plugin id via the plugins list (GET /v1/plugins?status=active) and correct the URL/route.
- If the plugin was deleted, clear the stale link/query cache and navigate back to the plugin list.
- Check Den server logs/health when the status is 5xx; retry after the server recovers.
Example fix
// before: both requests fire even if the first fails
const [pluginResult, membershipsResult] = await Promise.all([...]);
if (!pluginResult.response.ok) {
throw new Error(getErrorMessage(pluginResult.payload, `Failed to load plugin (${pluginResult.response.status}).`));
}
// after: short-circuit so a 404 doesn't also burn the memberships call
const pluginResult = await requestJson(`/v1/plugins/${encodeURIComponent(id)}`, { method: "GET" }, 15000);
if (!pluginResult.response.ok) {
throw new Error(getErrorMessage(pluginResult.payload, `Failed to load plugin (${pluginResult.response.status}).`));
}
const membershipsResult = await requestJson(`/v1/plugins/${encodeURIComponent(id)}/resolved`, { method: "GET" }, 15000); Defensive patterns
Strategy: try-catch
Validate before calling
const idOk = typeof id === "string" && id.length > 0; const sessionOk = await ensureSession(); // redirect on 401 before issuing the request if (!idOk || !sessionOk) return null;
Type guard
function isPluginPayload(p: unknown): p is { item: Record<string, unknown> } {
return typeof p === "object" && p !== null && isRecord((p as { item?: unknown }).item);
} Try / catch
try {
const plugin = await fetchResolvedPlugin(id);
} catch (err) {
const m = err instanceof Error ? err.message : "";
if (m.includes("(404)")) { notFound(); return null; }
if (m.includes("(401)") || m.includes("(403)")) { await redirectToSignIn(); return null; }
showToast("Failed to load plugin; please retry.");
} Prevention
- Short-circuit the second request when the first fails to avoid wasted calls and clearer errors.
- Treat 404 as 'plugin gone': clear stale cached routes and query keys pointing at deleted ids.
- Keep den-web and Den server versions in lockstep; version-gate new endpoints.
- Use TanStack Query's error state to render a retry button instead of crashing the route.
When it happens
Trigger: GET /v1/plugins/{id} returns 401/403 (session expired, no permission on the plugin's org), 404 (plugin deleted or id mistyped), or 5xx from the Den server. Note both requests run unconditionally; the plugin fetch is checked first.
Common situations: Opening a bookmarked plugin route after the plugin was unpublished; stale pluginQueryKeys cache pointing at a deleted id; den-web pointed at a Den server in a different org/environment; server restart or migration in progress causing 5xx.
Related errors
- Failed to load plugin access (${response.status}).
- Failed to load plugin contents (${membershipsResult.response
- Failed to load plugins (${response.status}).
- 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/61e63ab40bcc433e.
Report an issue: GitHub.