{"record":{"id":"e0fbb4dc53f81ce8","repo":"odysseus-dev/odysseus","slug":"d-error-failed-to-create-calendar","errorCode":null,"errorMessage":"d.error || 'Failed to create calendar'","messagePattern":"d\\.error \\|\\| 'Failed to create calendar'","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/calendar.js","lineNumber":2625,"sourceCode":"    if (_open) _render();\n  });\n  _sunBtn?.addEventListener('click', () => {\n    _weekStartSun = true;\n    localStorage.setItem('cal-week-start', 'sun');\n    _applyWeekStartActive();\n    if (_open) _render();\n  });\n\n  // Create a new (local) calendar. Defaults the name + next palette color, then\n  // reopens the panel so the user can rename it inline and pick a color.\n  overlay.querySelector('#cal-settings-add')?.addEventListener('click', async (e) => {\n    const btn = e.currentTarget;\n    btn.disabled = true;\n    const color = COLORS[_calendars.length % COLORS.length];\n    try {\n      const r = await fetch(`${API_BASE}/api/calendar/calendars?name=${encodeURIComponent('New calendar')}&color=${encodeURIComponent(color)}`, { method: 'POST', credentials: 'same-origin' });\n      const d = await r.json().catch(() => ({}));\n      if (!r.ok || !d.ok) throw new Error(d.error || 'Failed to create calendar');\n      _calendars.push({ name: d.name, href: d.id, color: d.color });\n      _allEvents = {}; _fetchedRanges = []; localStorage.removeItem(LS_KEY);\n      _render();\n      cleanup();\n      _showCalSettings();\n      // Focus the new row's name field so it's ready to rename.\n      setTimeout(() => {\n        const rows = document.querySelectorAll('#cal-settings-list .cal-settings-row');\n        const last = rows[rows.length - 1];\n        const nm = last?.querySelector('.cal-s-name');\n        if (nm) { nm.focus(); nm.select(); }\n      }, 30);\n    } catch (err) {\n      btn.disabled = false;\n      if (window.showError) window.showError(err.message || 'Failed to create calendar');\n      else console.error(err);\n    }\n  });","sourceCodeStart":2607,"sourceCodeEnd":2643,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/calendar.js#L2607-L2643","documentation":"HTTP 500 from GET /{file_id}/vision when analyze_image_with_vl (src/document_processor.py:390) raises while computing OCR/description — e.g. the vision model is unreachable, credentials invalid, or image decoding fails. The underlying exception is interpolated into the message ('Vision analysis failed: {e}') after being logged, so the response carries the root cause. The cache write afterward is best-effort and never 500s.","triggerScenarios":"First (uncached) GET /vision for an image while the vision model API is down, the API key is invalid/quota-exhausted, the image is corrupt so decode fails, or the model returns a payload the parser rejects. Cached entries (UPLOAD_DIR/.vision/{id}.txt) never hit this path unless force=1.","commonSituations":"Missing/rotated VL model credentials in deployment env; vision provider rate limit or outage; corrupt upload (partial write) so PIL/opening the file throws; forcing recompute with ?force=1 during an outage when a good cache existed.","solutions":["Read the response body / server log — the embedded {e} names the actual failure (auth error, timeout, decode error).","Fix the vision model configuration (endpoint URL, API key, quota) in the environment and retry without force=1 so any existing cache is used.","If the image itself is corrupt, re-upload the source file; a partially-written upload will fail decode every time.","Avoid force=1 in automated flows — it bypasses the cache and exposes every request to model availability.","Treat this as transient where applicable: retry with backoff after restoring model access."],"exampleFix":"// before\nconst r = await fetch(`/api/upload/${id}/vision?force=1`);\n\n// after\nconst r = await fetch(`/api/upload/${id}/vision`); // prefer cache; force only on user action","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function getVisionText(id, { force = false, retries = 2 } = {}) {\n  for (let i = 0; i <= retries; i++) {\n    const r = await fetch(`/api/upload/${id}/vision${force ? '?force=1' : ''}`, { credentials: 'include' });\n    if (r.ok) return (await r.json()).text;\n    if (r.status === 500 && i < retries) { await sleep(1000 * 2 ** i); continue; }\n    throw new Error(`vision failed: ${await r.text()}`); // body carries root cause\n  }\n}","preventionTips":["Prefer the cache (omit force=1) whenever a cached result is acceptable","Surface the embedded {e} message to operators — it distinguishes auth vs outage vs corrupt image","Retry with backoff for transient model outages; fail fast on auth errors","Validate the model endpoint and key in deployment smoke tests"],"tags":["vision","http-500","external-service","ocr"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}