{"record":{"id":"a3d89fa1c3a05dd8","repo":"odysseus-dev/odysseus","slug":"http-r-status","errorCode":null,"errorMessage":"HTTP ' + r.status","messagePattern":"HTTP ' \\+ r\\.status","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"static/js/calendar.js","lineNumber":258,"sourceCode":"    // Per-event color override (including the bg:<url> sentinel for custom\n    // backgrounds) wins over the parent calendar's default hex.\n    color: (data.color !== undefined && data.color !== null) ? data.color : (cal?.color || ''),\n  };\n}\n\n// v2 review error-handling MEDs: every fetch here previously checked\n// only `.then(r => r.json())` with no `r.ok` test. A 500/404 still\n// resolved the promise and the optimistic state got promoted to truth.\n// All three flows now inspect `r.ok` and roll back the optimistic\n// state + surface a toast on the failure path.\nasync function _createEvent(data) {\n  const tempUid = 'temp-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8);\n  _allEvents[tempUid] = _optimisticEvent(data, tempUid);\n  fetch(`${API_BASE}/api/calendar/events`, {\n    method: 'POST', credentials: 'same-origin',\n    headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data),\n  }).then(async r => {\n    if (!r.ok) throw new Error('HTTP ' + r.status);\n    return r.json();\n  }).then(d => {\n    if (d.uid) {\n      delete _allEvents[tempUid];\n      _allEvents[d.uid] = _optimisticEvent(data, d.uid);\n      _saveCache && _saveCache();\n      if (_open) _render();\n    }\n  }).catch((e) => {\n    delete _allEvents[tempUid];\n    if (_open) _render();\n    if (window.uiModule) window.uiModule.showError('Failed to create event: ' + (e?.message || 'unknown'));\n  });\n  return { uid: tempUid };\n}\n\nasync function _updateEvent(uid, data) {\n  const merged = { ...(_allEvents[uid] || {}), ...data };","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/calendar.js#L240-L276","documentation":"HTTP 403 from GET /{file_id}/vision when authentication is configured and effective_user(request) returns no authenticated user. The vision route mirrors the download route's owner-or-admin policy, and anonymous callers are rejected before cache lookup or OCR computation (vision calls can be expensive, so gating early matters).","triggerScenarios":"GET /api/upload/{id}/vision without a session/credential while auth is configured; expired session token; server-side fetch to the vision endpoint missing the auth cookie.","commonSituations":"Frontend prefetching vision text before login completes; incognito access to a shared link; token invalidation after an auth-secret rotation.","solutions":["Send valid credentials with the request (session cookie or token) — same auth context as the chat UI uses.","Ensure the client only calls /vision after a successful auth handshake.","Re-login if the session expired; check cookie attributes if the call comes from a different origin.","Confirm whether auth is intentionally configured — an unexpected users/auth config flips this endpoint from open to 403."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const session = await getSession();\nif (!session?.user) { await login(); } // then call /vision","typeGuard":null,"tryCatchPattern":"try {\n  const r = await fetch(`/api/upload/${id}/vision`, { credentials: 'include' });\n  if (r.status === 403) { promptLogin(); return; }\n} catch (e) { /* offline handling */ }","preventionTips":["Only trigger OCR prefetch after login completes","Include session credentials on all /vision calls"],"tags":["auth","http-403","vision","session"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}