{"record":{"id":"f50caa9624035c28","repo":"odysseus-dev/odysseus","slug":"failed-f50caa","errorCode":null,"errorMessage":"Failed","messagePattern":"Failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/settings.js","lineNumber":2098,"sourceCode":"  const saveBtn = el('settings-pw-save');\n  const msgEl = el('settings-pw-msg');\n  if (saveBtn) {\n    saveBtn.addEventListener('click', async () => {\n      const cur = el('settings-pw-current').value;\n      const nw = el('settings-pw-new').value;\n      const conf = el('settings-pw-confirm').value;\n      msgEl.style.color = '';\n      if (!cur || !nw) { msgEl.textContent = 'Fill in all fields'; msgEl.style.color = 'var(--red)'; return; }\n      if (nw.length < _authPolicy.password_min_length) { msgEl.textContent = `Min ${_authPolicy.password_min_length} characters`; msgEl.style.color = 'var(--red)'; return; }\n      if (nw !== conf) { msgEl.textContent = 'Passwords don\\'t match'; msgEl.style.color = 'var(--red)'; return; }\n      saveBtn.disabled = true;\n      try {\n        const res = await fetch('/api/auth/change-password', {\n          method: 'POST', credentials: 'same-origin',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify({ current_password: cur, new_password: nw })\n        });\n        if (!res.ok) { const d = await res.json(); throw new Error(d.detail || 'Failed'); }\n        msgEl.style.color = 'var(--green)';\n        msgEl.textContent = 'Password updated';\n        el('settings-pw-current').value = '';\n        el('settings-pw-new').value = '';\n        el('settings-pw-confirm').value = '';\n      } catch (e) {\n        msgEl.style.color = 'var(--red)';\n        msgEl.textContent = e.message;\n      } finally {\n        saveBtn.disabled = false;\n      }\n    });\n  }\n\n  // ── Two-Factor Authentication ──\n  const tfaContent = el('settings-2fa-content');\n  if (tfaContent) {\n    async function render2FA() {","sourceCodeStart":2080,"sourceCodeEnd":2116,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/settings.js#L2080-L2116","documentation":"Fallback error for a failed password change. The handler POSTs {current_password, new_password} to /api/auth/change-password; on non-OK it tries d.detail and falls back to the bare string 'Failed' when the error body has no detail field. Client-side validation (min length, match) has already run before this point.","triggerScenarios":"POST /api/auth/change-password returns non-2xx: wrong current password, new password violating server-side policy (complexity, reuse), 401 session expired — with a body lacking a usable detail field; or the fetch itself rejects (offline).","commonSituations":"User mistypes current password; server password policy stricter than the client checks; expired login; proxy strips the JSON error body.","solutions":["Verify current password is correct and the new one meets server policy","Check the response body in devtools — the real reason is usually there but discarded","Include res.status in the thrown error so 'Failed' becomes actionable","Catch fetch/network rejections separately from HTTP errors"],"exampleFix":"// before\n        if (!res.ok) { const d = await res.json(); throw new Error(d.detail || 'Failed'); }\n\n// after\n        if (!res.ok) {\n          const d = await res.json().catch(() => ({}));\n          throw new Error(d.detail || `Failed (HTTP ${res.status})`);\n        }","handlingStrategy":"try-catch","validationCode":"if (!cur || !nw || nw !== conf) return;\nif (nw.length < _authPolicy.password_min_length) return;\nif (!navigator.onLine) { msgEl.textContent = 'Offline'; return; }","typeGuard":null,"tryCatchPattern":"try { const res = await fetch(...); if (!res.ok) { const d = await res.json().catch(() => ({})); throw new Error(d.detail || `Failed (HTTP ${res.status})`); } ... } catch (e) { msgEl.textContent = e.message; }","preventionTips":["Guard res.json() — unparseable bodies currently throw SyntaxError","Mirror the server's full password policy client-side","Include res.status in fallback errors","Keep the saveBtn disable/enable in finally (already correct)"],"tags":["auth","password","fetch","error-handling"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}