{"record":{"id":"3644c14f7585aa22","repo":"infiniflow/ragflow","slug":"failed-to-fetch-skills","errorCode":null,"errorMessage":"Failed to fetch skills","messagePattern":"Failed to fetch skills","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/pages/skills/hooks.ts","lineNumber":656,"sourceCode":"        // Use search API with empty query to list all skills\n        const response = await fetch('/api/v1/skills/search', {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            Authorization: getAuthorization(),\n          },\n          body: JSON.stringify({\n            space_id: spaceId,\n            query: '', // Empty query = list all\n            page,\n            page_size: pageSize,\n            sort_by: sortBy,\n            sort_order: sortOrder,\n          }),\n        });\n\n        if (!response.ok) {\n          throw new Error('Failed to fetch skills');\n        }\n\n        const result = await response.json();\n        if (result.code !== 0) {\n          throw new Error(result.message || 'Failed to fetch skills');\n        }\n\n        const searchSkills = result.data?.skills || [];\n        const total = result.data?.total || 0;\n\n        // If search returned results, use them\n        if (searchSkills.length > 0) {\n          const skillsData: Skill[] = searchSkills.map((result: any) => {\n            const timestamp = pickSkillTimestamp(result);\n            const skillId = result.skill_id || result.name;\n\n            return {\n              id: skillId,","sourceCodeStart":638,"sourceCodeEnd":674,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/web/src/pages/skills/hooks.ts#L638-L674","documentation":"Thrown in web/src/pages/skills/hooks.ts:656 when the raw fetch() to the skill-search endpoint returns a non-OK HTTP status (!response.ok). Unlike other paths that use axios-style services, this hook calls fetch directly, so transport-level failures (404, 401, 500, gateway timeout) surface here before any JSON body is parsed. It indicates the request never reached a successful application-level response.","triggerScenarios":"The skill search API route is not deployed (404 after a partial backend upgrade); auth middleware returns 401/403; reverse proxy returns 502/504 when the backend service is down; CORS block in dev when the frontend origin is not allowed; malformed URL built from spaceId.","commonSituations":"Frontend deployed against an older backend without the search-skills endpoint. Backend container restarting during the request. Dev-server proxy misconfiguration. Session cookie missing after logout.","solutions":["Check response.status in the thrown error path (include it in the message) to distinguish 401/404/5xx","Verify the backend version exposes the skill search endpoint the hook targets","Confirm the auth token/cookie is attached to the fetch (credentials/headers)","Test the endpoint directly with curl to see whether the failure is frontend- or server-side","If 502/504, wait for the backend to be healthy and retry"],"exampleFix":"// before\nif (!response.ok) {\n  throw new Error('Failed to fetch skills');\n}\n\n// after\nif (!response.ok) {\n  const body = await response.text().catch(() => '');\n  throw new Error(\n    `Failed to fetch skills (HTTP ${response.status}) ${body.slice(0, 200)}`,\n  );\n}","handlingStrategy":"retry","validationCode":"const endpointOk = async (url: string) =>\n  (await fetch(url, { method: 'HEAD' }).catch(() => ({ ok: false }))).ok;","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(url, opts);\n  if (!res.ok) throw new Error(`HTTP ${res.status}`);\n} catch (e) {\n  if (e.message.includes('HTTP 5')) await backoffRetry();\n  else throw e;\n}","preventionTips":["Route this call through the shared request client so auth headers and error mapping are consistent","Include response.status in every thrown HTTP error for diagnosability"],"tags":["fetch-api","http-status","skills","network"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}