{"record":{"id":"beddb0ef304a313a","repo":"decolua/9router","slug":"failed-to-save-mappings","errorCode":null,"errorMessage":"Failed to save mappings","messagePattern":"Failed to save mappings","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js","lineNumber":215,"sourceCode":"      ...prev,\n      [alias]: value,\n    }));\n  };\n\n  const handleSaveMappings = async () => {\n    setLoading(true);\n    setMessage(null);\n\n    try {\n      const res = await fetch(\"/api/cli-tools/antigravity-mitm/alias\", {\n        method: \"PUT\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: JSON.stringify({ tool: \"antigravity\", mappings: modelMappings }),\n      });\n\n      if (!res.ok) {\n        const data = await res.json();\n        throw new Error(data.error || \"Failed to save mappings\");\n      }\n\n      setMessage({ type: \"success\", text: \"Mappings saved!\" });\n    } catch (error) {\n      setMessage({ type: \"error\", text: error.message });\n    } finally {\n      setLoading(false);\n    }\n  };\n\n  const isRunning = status?.running;\n\n  return (\n    <Card padding=\"xs\" className=\"overflow-hidden\">\n      <div className=\"flex items-start justify-between gap-3 hover:cursor-pointer sm:items-center\" onClick={onToggle}>\n        <div className=\"flex min-w-0 items-center gap-3\">\n          <div className=\"size-8 flex items-center justify-center shrink-0\">\n            <Image","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js#L197-L233","documentation":"AntigravityToolCard's handleSaveMappings throws this generic message when the POST to the cli-tools mappings API returns a non-2xx response and the response body carries no specific `error` field. It is a client-side fallback message surfaced in the card's message banner; the real cause is whatever the server rejected the save with (auth failure, invalid model name, backend write error).","triggerScenarios":"POST /api/cli-tools/... with {tool:'antigravity', mappings} fails: session expired (401), a mapping references a model the backend cannot resolve (400), or the persistence layer errors while writing mappings (500); also thrown verbatim when res.ok is false and data.error is empty.","commonSituations":"Dashboard session cookie expired mid-edit; pasted a mapping whose target model id was renamed or removed; SQLite DB locked or read-only; server restarted between opening the card and clicking save.","solutions":["Check the server response/network tab for the actual status code and error body from the mappings endpoint","Re-authenticate (log back into the dashboard) and retry the save","Validate that every mapping's model id still exists in the provider's model list before saving","Verify the DB is writable (check ~/.9router permissions / disk space) and the server log for the underlying write error"],"exampleFix":"// before\nif (!res.ok) {\n  const data = await res.json();\n  throw new Error(data.error || \"Failed to save mappings\");\n}\n// after\nif (!res.ok) {\n  const data = await res.json().catch(() => ({}));\n  if (res.status === 401) throw new Error(\"Session expired — please log in again\");\n  throw new Error(data.error || `Failed to save mappings (HTTP ${res.status})`);\n}","handlingStrategy":"try-catch","validationCode":"// before saving\nconst invalid = Object.entries(modelMappings).filter(([k, v]) => !k || !v);\nif (invalid.length) throw new Error(\"All mappings must have non-empty source and target\");","typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetch(\"/api/cli-tools/...\", { method: \"POST\", body: JSON.stringify({ tool: \"antigravity\", mappings: modelMappings }) });\n  const data = res.ok ? await res.json() : await res.json().catch(() => ({}));\n  if (!res.ok) throw new Error(data.error || `Failed to save mappings (HTTP ${res.status})`);\n} catch (e) {\n  setMessage({ type: \"error\", text: e.message });\n}","preventionTips":["Re-login before long editing sessions to avoid expired sessions","Validate model ids against the provider's current model list before saving","Show res.status in the error banner to make failures diagnosable","Check server logs when saves repeatedly fail"],"tags":["http","api-client","persistence","dashboard"],"backgroundTag":"http-request-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}