{"record":{"id":"30ec9bba0eecf5a6","repo":"different-ai/openwork","slug":"failed-to-load-plugins-response-status","errorCode":null,"errorMessage":"Failed to load plugins (${response.status}).","messagePattern":"Failed to load plugins \\((.+?)\\)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ee/apps/den-web/app/(den)/dashboard/_components/plugin-data.tsx","lineNumber":725,"sourceCode":"    workflows,\n    skills,\n    slug: slugifyPluginName(name),\n    source: marketplaces[0]\n      ? { type: \"marketplace\", marketplace: marketplaces[0].name }\n      : { type: \"github\", repo: \"Connected repository\" },\n    status: asString(pluginItem.status) === \"archived\" ? \"archived\" : \"active\",\n    updatedAt: asString(pluginItem.updatedAt) ?? new Date().toISOString(),\n    version: null,\n  } satisfies DenPlugin;\n}\n\nexport function usePlugins() {\n  return useQuery({\n    queryKey: pluginQueryKeys.list(),\n    queryFn: async () => {\n      const { response, payload } = await requestJson(\"/v1/plugins?status=active&limit=100\", { method: \"GET\" }, 20000);\n      if (!response.ok) {\n        throw new Error(getErrorMessage(payload, `Failed to load plugins (${response.status}).`));\n      }\n\n      const items = isRecord(payload) && Array.isArray(payload.items) ? payload.items : [];\n      const pluginIds = items.flatMap((entry) => {\n        const id = isRecord(entry) ? asString(entry.id) : null;\n        return id ? [id] : [];\n      });\n\n      const plugins = await Promise.all(pluginIds.map((id) => fetchResolvedPlugin(id)));\n      return plugins.filter((plugin): plugin is DenPlugin => Boolean(plugin));\n    },\n  });\n}\n\nexport function usePlugin(id: string) {\n  return useQuery({\n    queryKey: pluginQueryKeys.detail(id),\n    queryFn: async () => fetchResolvedPlugin(id),","sourceCodeStart":707,"sourceCodeEnd":743,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-web/app/(den)/dashboard/_components/plugin-data.tsx#L707-L743","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: no retry, single throw on any non-ok\nconst { response, payload } = await requestJson(\"/v1/plugins?status=active&limit=100\", { method: \"GET\" }, 20000);\nif (!response.ok) {\n  throw new Error(getErrorMessage(payload, `Failed to load plugins (${response.status}).`));\n}\n// after: retry transient failures\nreturn useQuery({\n  queryKey: pluginQueryKeys.list(),\n  retry: (failureCount, error) => failureCount < 2,\n  queryFn: async () => {\n    const { response, payload } = await requestJson(\"/v1/plugins?status=active&limit=100\", { method: \"GET\" }, 20000);\n    if (!response.ok) throw new Error(getErrorMessage(payload, `Failed to load plugins (${response.status}).`));\n    ...\n  },\n});","handlingStrategy":"retry","validationCode":"const sessionOk = await ensureSession();\nif (!sessionOk) { await redirectToSignIn(); } // avoids guaranteed 401 on the list call","typeGuard":"function isPluginListPayload(p: unknown): p is { items: Array<{ id: string }> } {\n  if (typeof p !== \"object\" || p === null || !Array.isArray((p as { items?: unknown }).items)) return false;\n  return (p as { items: unknown[] }).items.every((e) => isRecord(e) && typeof e.id === \"string\");\n}","tryCatchPattern":"const { isPending, isError, error, refetch } = usePlugins();\nif (isError) {\n  const transient = /\\((5\\d\\d|429)\\)/.test(error.message);\n  if (transient) retryWithBackoff(refetch);\n  else if (error.message.includes(\"(401)\")) redirectToSignIn();\n  else renderEmptyState(error.message);\n}","preventionTips":["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."],"tags":["http","api-client","den-web","react-query"],"backgroundTag":"http-request-failed-with-status","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}