{"record":{"id":"16ba7685e005f756","repo":"sipeed/picoclaw","slug":"failed-to-fetch-config","errorCode":null,"errorMessage":"Failed to fetch config","messagePattern":"Failed to fetch config","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/frontend/src/components/config/raw-config-page.tsx","lineNumber":36,"sourceCode":"  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"@/components/ui/alert-dialog\"\nimport { Button } from \"@/components/ui/button\"\nimport { Textarea } from \"@/components/ui/textarea\"\nimport { showSaveSuccessOrRestartToast } from \"@/lib/restart-required\"\nimport { refreshGatewayState } from \"@/store/gateway\"\n\nexport function RawConfigPage() {\n  const { t } = useTranslation()\n  const queryClient = useQueryClient()\n\n  const { data: config, isLoading } = useQuery({\n    queryKey: [\"config\"],\n    queryFn: async () => {\n      const res = await launcherFetch(\"/api/config\")\n      if (!res.ok) {\n        throw new Error(\"Failed to fetch config\")\n      }\n      return res.json()\n    },\n  })\n\n  const mutation = useMutation({\n    mutationFn: async (newConfig: string) => {\n      const res = await launcherFetch(\"/api/config\", {\n        method: \"PUT\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: newConfig,\n      })\n      if (!res.ok) {\n        throw new Error(\"Failed to save config\")\n      }\n    },\n    onSuccess: (_, submittedConfig) => {\n      try {","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/frontend/src/components/config/raw-config-page.tsx#L18-L54","documentation":"Thrown in the react-query queryFn of RawConfigPage (web/frontend/src/components/config/raw-config-page.tsx:36) when GET /api/config via launcherFetch resolves with a non-2xx status. Same endpoint and same launcherFetch wrapper as ConfigPage's loader, but without the 5-second timeout this page uses. 401 JSON responses normally redirect to /launcher-login before this throw, except on auth pages.","triggerScenarios":"Backend returns 500 (config unreadable/corrupt on disk), 404 from a misrouted reverse proxy, 403, or 401 while on the login/setup page; opening /raw config editor right after a backend restart.","commonSituations":"Config file permissions broken after an edit; proxy rules covering /api but pointing to the wrong upstream; expired session cookie; backend crashed and the proxy answers 502.","solutions":["Check the network tab for the exact status of GET /api/config and read the response body","For 500: inspect launcher backend logs and repair the config file on disk (permissions, syntax)","For proxy errors (404/502): correct routing so /api/config hits the launcher backend","For 401: authenticate at /launcher-login first"],"exampleFix":"// before\nconst res = await launcherFetch(\"/api/config\")\nif (!res.ok) {\n  throw new Error(\"Failed to fetch config\")\n}\n\n// after\nconst res = await launcherFetch(\"/api/config\")\nif (!res.ok) {\n  const body = await res.text().catch(() => \"\")\n  throw new Error(`Failed to fetch config (HTTP ${res.status})${body ? \": \" + body : \"\"}`)\n}","handlingStrategy":"retry","validationCode":"async function configReachable(): Promise<boolean> {\n  try {\n    const res = await launcherFetch(\"/api/config\")\n    return res.ok\n  } catch {\n    return false\n  }\n}","typeGuard":null,"tryCatchPattern":"// let react-query expose query.error; enrich with status\nconst res = await launcherFetch(\"/api/config\")\nif (!res.ok) {\n  const body = await res.text().catch(() => \"\")\n  throw new Error(`Failed to fetch config (HTTP ${res.status})${body ? \": \" + body : \"\"}`)\n}","preventionTips":["Run deployment smoke tests that GET /api/config through the same proxy path users hit","Keep the config file readable by the backend user; alert on permission drift","Include status codes in thrown errors to cut diagnosis time"],"tags":["network","http","frontend","react-query","config","api"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}