odysseus-dev/odysseus · warning · Error

HTTP ' + r.status

Error message

HTTP ' + r.status

What it means

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).

Solutions

  1. Send valid credentials with the request (session cookie or token) — same auth context as the chat UI uses.
  2. Ensure the client only calls /vision after a successful auth handshake.
  3. Re-login if the session expired; check cookie attributes if the call comes from a different origin.
  4. Confirm whether auth is intentionally configured — an unexpected users/auth config flips this endpoint from open to 403.
Defensive patterns

Strategy: validation

Validate before calling

const session = await getSession();
if (!session?.user) { await login(); } // then call /vision

Try / catch

try {
  const r = await fetch(`/api/upload/${id}/vision`, { credentials: 'include' });
  if (r.status === 403) { promptLogin(); return; }
} catch (e) { /* offline handling */ }

Prevention

When it happens

Trigger: 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.

Common situations: Frontend prefetching vision text before login completes; incognito access to a shared link; token invalidation after an auth-secret rotation.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/a3d89fa1c3a05dd8. Report an issue: GitHub.

Appendix: source

Thrown at static/js/calendar.js:258

    // Per-event color override (including the bg:<url> sentinel for custom
    // backgrounds) wins over the parent calendar's default hex.
    color: (data.color !== undefined && data.color !== null) ? data.color : (cal?.color || ''),
  };
}

// v2 review error-handling MEDs: every fetch here previously checked
// only `.then(r => r.json())` with no `r.ok` test. A 500/404 still
// resolved the promise and the optimistic state got promoted to truth.
// All three flows now inspect `r.ok` and roll back the optimistic
// state + surface a toast on the failure path.
async function _createEvent(data) {
  const tempUid = 'temp-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8);
  _allEvents[tempUid] = _optimisticEvent(data, tempUid);
  fetch(`${API_BASE}/api/calendar/events`, {
    method: 'POST', credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data),
  }).then(async r => {
    if (!r.ok) throw new Error('HTTP ' + r.status);
    return r.json();
  }).then(d => {
    if (d.uid) {
      delete _allEvents[tempUid];
      _allEvents[d.uid] = _optimisticEvent(data, d.uid);
      _saveCache && _saveCache();
      if (_open) _render();
    }
  }).catch((e) => {
    delete _allEvents[tempUid];
    if (_open) _render();
    if (window.uiModule) window.uiModule.showError('Failed to create event: ' + (e?.message || 'unknown'));
  });
  return { uid: tempUid };
}

async function _updateEvent(uid, data) {
  const merged = { ...(_allEvents[uid] || {}), ...data };

View on GitHub (pinned to f9235ebbf1)