{"record":{"id":"323e9d79bbbb18b6","repo":"decolua/9router","slug":"failed-to-update-quota-visibility","errorCode":null,"errorMessage":"Failed to update quota visibility","messagePattern":"Failed to update quota visibility","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js","lineNumber":584,"sourceCode":"      await fetch(\"/api/settings\", {\n        method: \"PATCH\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: JSON.stringify({ [settingsKey]: cfg }),\n      });\n    } catch {\n      setAutoPingMaps(previous);\n    }\n  }, [autoPingMaps]);\n\n  const updateQuotaVisibility = useCallback(async (nextVisibility, previousVisibility) => {\n    setQuotaVisibility(nextVisibility);\n    try {\n      const response = await fetch(\"/api/settings\", {\n        method: \"PATCH\",\n        headers: { \"Content-Type\": \"application/json\" },\n        body: JSON.stringify({ quotaVisibility: nextVisibility }),\n      });\n      if (!response.ok) throw new Error(\"Failed to update quota visibility\");\n    } catch (error) {\n      console.error(\"Error updating quota visibility:\", error);\n      setQuotaVisibility(previousVisibility);\n    }\n  }, []);\n\n  const handleHideQuota = useCallback((provider, quota) => {\n    const key = getQuotaVisibilityKey(quota);\n    if (!provider || !key) return;\n\n    const previous = quotaVisibility;\n    const providerVisibility = previous[provider] || {};\n    const hidden = new Set(providerVisibility.hidden || []);\n    hidden.add(key);\n    const next = {\n      ...previous,\n      [provider]: {\n        ...providerVisibility,","sourceCodeStart":566,"sourceCodeEnd":602,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js#L566-L602","documentation":"ProviderLimits persists per-provider quota visibility (hidden rows) by PATCHing /api/settings with { quotaVisibility }. On any non-OK response it throws 'Failed to update quota visibility', logs the error, and rolls the local visibility state back to previousVisibility. Note the thrown message is fixed — the actual HTTP status is only visible in the console.error.","triggerScenarios":"PATCH /api/settings with body { quotaVisibility: nextVisibility } returns non-OK — most commonly 401 when the dashboard session expired, 400 on body validation, or 500 when persisting settings to the SQLite store fails — inside the visibility toggle handler at ProviderLimits/index.js:584.","commonSituations":"Dashboard session expired mid-use (JWT_SECRET rotated or cookie expired); settings DB write failure (read-only data dir, disk full); server restarting while the toggle is clicked.","solutions":["Log back into the dashboard — 401 from /api/settings means the session cookie is no longer valid.","Check server logs for settings-save failures and ensure the data directory (~/.9router or DATA_DIR) is writable.","Retry the toggle; the UI already reverts the optimistic change, so a retry after re-login is safe.","Include response.status in the thrown message so failures are diagnosable without the console."],"exampleFix":"// before\nif (!response.ok) throw new Error(\"Failed to update quota visibility\");\n// after\nif (!response.ok) throw new Error(`Failed to update quota visibility (HTTP ${response.status})`);","handlingStrategy":"try-catch","validationCode":"const sessionOk = await fetch(\"/api/settings\", { method: \"GET\" }).then((r) => r.ok).catch(() => false);\nif (!sessionOk) { redirectToLogin(); return; }\nconst isValidVisibility = (v) => v && typeof v === \"object\" && Object.values(v).every((b) => typeof b === \"boolean\");","typeGuard":"const isVisibilityMap = (v) => v !== null && typeof v === \"object\" && !Array.isArray(v) && Object.values(v).every((x) => typeof x === \"boolean\");","tryCatchPattern":"const previousVisibility = quotaVisibility;\nsetQuotaVisibility(nextVisibility);\ntry {\n  const response = await fetch(\"/api/settings\", { method: \"PATCH\", headers: { \"Content-Type\": \"application/json\" }, body: JSON.stringify({ quotaVisibility: nextVisibility }) });\n  if (response.status === 401) { redirectToLogin(); return; }\n  if (!response.ok) throw new Error(`Failed to update quota visibility (HTTP ${response.status})`);\n} catch (error) {\n  console.error(\"Error updating quota visibility:\", error);\n  setQuotaVisibility(previousVisibility);\n}","preventionTips":["Always keep the previous value to roll back the optimistic update (the code already does this).","Re-authenticate on 401 rather than silently reverting.","Verify the settings DB is writable when PATCHes repeatedly fail."],"tags":["http-error","authentication","settings"],"backgroundTag":"http-401-unauthorized","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}