{"record":{"id":"e58537c580eedc41","repo":"mudler/LocalAI","slug":"unknown-error","errorCode":null,"errorMessage":"unknown error","messagePattern":"unknown error","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"core/http/react-ui/src/pages/Middleware.jsx","lineNumber":320,"sourceCode":"// shows every available detector, so admins toggle defaults instead of retyping\n// names, and link straight to each detector's config to edit its policy.\nfunction DetectorModels({ pii, addToast, onChanged }) {\n  const navigate = useNavigate()\n  const location = useLocation()\n  const rows = useMemo(() => pii.detector_models || [], [pii.detector_models])\n  // Names currently in the default set; the toggle adds/removes against this.\n  const defaults = useMemo(() => pii.default_detectors || [], [pii.default_detectors])\n  // Track which rows are mid-save to disable just that toggle (optimistic).\n  const [busy, setBusy] = useState(() => new Set())\n\n  const toggleDefault = async (name, on) => {\n    const next = on\n      ? [...new Set([...defaults, name])]\n      : defaults.filter(d => d !== name)\n    setBusy(prev => new Set(prev).add(name))\n    try {\n      const body = await settingsApi.save({ pii_default_detectors: next })\n      if (body && body.success === false) throw new Error(body.error || 'unknown error')\n      addToast?.(on ? `${name} added to default detectors` : `${name} removed from default detectors`, 'success')\n      onChanged?.()\n    } catch (err) {\n      addToast?.(`Failed to save: ${err.message}`, 'error')\n    } finally {\n      setBusy(prev => { const n = new Set(prev); n.delete(name); return n })\n    }\n  }\n\n  return (\n    <div className=\"card pad-md mb-md\">\n      <div className=\"hstack hstack--between mb-sm\">\n        <span className=\"text-base fw-semibold\">Detector models</span>\n        <button\n          className=\"btn btn-secondary btn-sm\"\n          onClick={() => navigate('/app/model-editor?template=secret-filter', { state: fromState(location, 'Middleware') })}\n          title=\"Add a NER or pattern detector model\"\n        >","sourceCodeStart":302,"sourceCodeEnd":338,"githubUrl":"https://github.com/mudler/LocalAI/blob/44413a9d06bf5bc52ce088ba8ca74e5a2e8bee26/core/http/react-ui/src/pages/Middleware.jsx#L302-L338","documentation":"Thrown by toggleDefault() in the PII default-detectors card when settingsApi.save({pii_default_detectors: next}) resolves with body.success === false and body.error is empty/undefined. The 'unknown error' string is the fallback placeholder, meaning the settings API rejected the write but returned no error message. The busy-row Set is always cleared in finally, so the toggle re-enables after failure.","triggerScenarios":"POST/PUT to the settings endpoint returns {success:false} with no error field — e.g. config file not writable, settings registry rejecting the pii_default_detectors key, or a validation failure server-side that omits the message.","commonSituations":"LocalAI config.yaml mounted read-only (Docker volume ro); a version mismatch where the running server does not know the pii_default_detectors key; settings backend returning success:false from a mutex/lock failure during concurrent saves.","solutions":["Open browser devtools network tab, find the settings save request, and inspect the raw response body","Check the config file LocalAI writes is writable (docker inspect mount modes; ls -l on the config path)","Verify the server version supports pii_default_detectors (compare with the settings registry in the running binary)","Retry the toggle after fixing writability; onChanged?.() is not called on failure so the UI stays consistent"],"exampleFix":"// before\nif (body && body.success === false) throw new Error(body.error || 'unknown error')\n\n// after: surface the HTTP status too when the API omits the message\nif (body && body.success === false) {\n  throw new Error(body.error || `settings save rejected (HTTP ${body.status ?? 'no status'}, no error message)`)\n}","handlingStrategy":"validation","validationCode":"// Validate the next detector list before saving: known names, no dupes, serializable\nfunction validDetectorList(next) {\n  return Array.isArray(next) && next.length > 0 && next.every(n => typeof n === 'string' && /^[\\w.-]+$/.test(n)) && new Set(next).size === next.length\n}","typeGuard":"function isSettingsReject(body) {\n  return !!body && typeof body === 'object' && body.success === false\n}","tryCatchPattern":"try {\n  const body = await settingsApi.save({ pii_default_detectors: next })\n  if (isSettingsReject(body)) throw new Error(body.error || 'server rejected pii_default_detectors without a reason')\n} catch (err) {\n  addToast?.(`Detector toggle failed: ${err.message}`, 'error')\n  // finally-block already restores the toggle; nothing else to roll back\n}","preventionTips":["Keep the config file writable by the LocalAI process","Mirror server-side validation rules client-side before saving","Log the raw response body when success===false arrives with no error field — that gap is a server-side bug worth reporting"],"tags":["settings","pii","react-ui","fallback-message"],"backgroundTag":null,"analyzedSha":"44413a9d06bf5bc52ce088ba8ca74e5a2e8bee26","analyzedAt":"2026-08-15T10:13:50.291Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}