{"record":{"id":"9a2f154b283d6cb9","repo":"mudler/LocalAI","slug":"http-r-status","errorCode":null,"errorMessage":"HTTP ${r.status}","messagePattern":"HTTP \\$\\{r\\.status\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"core/http/react-ui/src/utils/api.js","lineNumber":472,"sourceCode":"  entries: (name, userId) => fetchJSON(`/api/agents/collections/${enc(name)}/entries${userQ(userId)}`),\n  entryContent: (name, entry, userId) => fetchJSON(`/api/agents/collections/${enc(name)}/entries/${encodeURIComponent(entry)}${userQ(userId)}`),\n  search: (name, query, maxResults, userId) => postJSON(`/api/agents/collections/${enc(name)}/search${userQ(userId)}`, { query, max_results: maxResults }),\n  reset: (name, userId) => postJSON(`/api/agents/collections/${enc(name)}/reset${userQ(userId)}`),\n  deleteEntry: (name, entry, userId) => fetchJSON(`/api/agents/collections/${enc(name)}/entry/delete${userQ(userId)}`, { method: 'DELETE', body: JSON.stringify({ entry }), headers: { 'Content-Type': 'application/json' } }),\n  sources: (name, userId) => fetchJSON(`/api/agents/collections/${enc(name)}/sources${userQ(userId)}`),\n  addSource: (name, url, interval, userId) => postJSON(`/api/agents/collections/${enc(name)}/sources${userQ(userId)}`, { url, update_interval: interval }),\n  removeSource: (name, url, userId) => fetchJSON(`/api/agents/collections/${enc(name)}/sources${userQ(userId)}`, { method: 'DELETE', body: JSON.stringify({ url }), headers: { 'Content-Type': 'application/json' } }),\n}\n\n// Skills API\nexport const skillsApi = {\n  list: (allUsers) => fetchJSON(`/api/agents/skills${allUsers ? '?all_users=true' : ''}`),\n  search: (q) => fetchJSON(`/api/agents/skills/search?q=${enc(q)}`),\n  get: (name, userId) => fetchJSON(`/api/agents/skills/${enc(name)}${userQ(userId)}`),\n  create: (data) => postJSON('/api/agents/skills', data),\n  update: (name, data, userId) => fetchJSON(`/api/agents/skills/${enc(name)}${userQ(userId)}`, { method: 'PUT', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }),\n  delete: (name, userId) => fetchJSON(`/api/agents/skills/${enc(name)}${userQ(userId)}`, { method: 'DELETE' }),\n  import: (file) => { const fd = new FormData(); fd.append('file', file); return fetch(apiUrl('/api/agents/skills/import'), { method: 'POST', body: fd }).then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }); },\n  exportUrl: (name, userId) => apiUrl(`/api/agents/skills/export/${enc(name)}${userQ(userId)}`),\n  listResources: (name, userId) => fetchJSON(`/api/agents/skills/${enc(name)}/resources${userQ(userId)}`),\n  getResource: (name, path, opts, userId) => fetchJSON(`/api/agents/skills/${enc(name)}/resources/${path}${opts?.json ? '?encoding=base64' : ''}${userId ? `${opts?.json ? '&' : '?'}user_id=${enc(userId)}` : ''}`),\n  createResource: (name, path, file) => { const fd = new FormData(); fd.append('file', file); fd.append('path', path); return fetch(apiUrl(`/api/agents/skills/${enc(name)}/resources`), { method: 'POST', body: fd }).then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }); },\n  updateResource: (name, path, content) => postJSON(`/api/agents/skills/${enc(name)}/resources/${path}`, { content }),\n  deleteResource: (name, path) => fetchJSON(`/api/agents/skills/${enc(name)}/resources/${path}`, { method: 'DELETE' }),\n  listGitRepos: () => fetchJSON('/api/agents/git-repos'),\n  addGitRepo: (url) => postJSON('/api/agents/git-repos', { url }),\n  syncGitRepo: (id) => postJSON(`/api/agents/git-repos/${enc(id)}/sync`, {}),\n  toggleGitRepo: (id) => postJSON(`/api/agents/git-repos/${enc(id)}/toggle`, {}),\n  deleteGitRepo: (id) => fetchJSON(`/api/agents/git-repos/${enc(id)}`, { method: 'DELETE' }),\n}\n\n// Usage API\nexport const usageApi = {\n  getMyUsage: (period) => fetchJSON(`/api/auth/usage?period=${period || 'month'}`),\n  getAdminUsage: (period, userId) => {\n    let url = `/api/auth/admin/usage?period=${period || 'month'}`","sourceCodeStart":454,"sourceCodeEnd":490,"githubUrl":"https://github.com/mudler/LocalAI/blob/44413a9d06bf5bc52ce088ba8ca74e5a2e8bee26/core/http/react-ui/src/utils/api.js#L454-L490","documentation":"Thrown by skillsApi.import when the multipart FormData upload (file field) to POST /api/agents/skills/import returns a non-ok response. Unlike the JSON helpers, this inline fetch only throws the bare HTTP status without reading the error body, so the real server reason (usually a validation error on the skill archive) is hidden behind the status code.","triggerScenarios":"Uploading a skill bundle file that the server rejects: malformed zip, missing SKILL.md, skill name conflicts, file too large (413), or unauthenticated access to the admin endpoint (401/403). Also plain 404 when the agents/skills API is absent in the deployed version.","commonSituations":"Importing a hand-zipped skill directory that lacks the required manifest; importing into a LocalAI version predating the skills API; API-key protected instance; reverse proxy body-size limit (413) rejecting the archive.","solutions":["Check the status: 413 → increase proxy client_max_body_size / upload smaller archive; 401/403 → provide admin credentials; 400 → fix the archive structure (SKILL.md at root)","Re-zip the skill so SKILL.md sits at the archive root and re-import","Verify the endpoint exists: curl -i -F file=@skill.zip $BASE/api/agents/skills/import","Improve the helper to surface the server's JSON error message (see exampleFix)"],"exampleFix":"// before\nimport: (file) => { const fd = new FormData(); fd.append('file', file); return fetch(apiUrl('/api/agents/skills/import'), { method: 'POST', body: fd }).then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }); },\n\n// after — surface the server's reason\nimport: (file) => { const fd = new FormData(); fd.append('file', file); return fetch(apiUrl('/api/agents/skills/import'), { method: 'POST', body: fd }).then(async r => { if (!r.ok) { const d = await r.json().catch(() => ({})); throw new Error(d?.error?.message || `HTTP ${r.status}`); } return r.json(); }); },","handlingStrategy":"validation","validationCode":"// client-side pre-checks before upload\nif (!(file instanceof File) || file.size === 0) throw new Error('select a non-empty archive')\nif (!/\\.zip$/i.test(file.name)) throw new Error('skill import expects a .zip archive')","typeGuard":"function isSkillArchive(file) {\n  return file instanceof File && file.size > 0 && file.size < 50 * 1024 * 1024 && /\\.zip$/i.test(file.name)\n}","tryCatchPattern":"try { await skillsApi.import(file) } catch (e) {\n  const status = e.message.match(/HTTP (\\d+)/)?.[1]\n  if (status === '413') addToast('archive too large', 'error')\n  else if (status === '403') addToast('admin access required', 'error')\n  else addToast(e.message, 'error')\n}","preventionTips":["Validate file presence, extension, and size before POSTing","Include the API key header for admin endpoints","Patch the helper to read the server's JSON error so users see the real reason"],"tags":["http","skills","file-upload","multipart","agents"],"backgroundTag":null,"analyzedSha":"44413a9d06bf5bc52ce088ba8ca74e5a2e8bee26","analyzedAt":"2026-08-15T10:13:50.291Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}