{"record":{"id":"32923a88287095cc","repo":"odysseus-dev/odysseus","slug":"tidy-failed","errorCode":null,"errorMessage":"Tidy failed","messagePattern":"Tidy failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"static/js/documentLibrary.js","lineNumber":2293,"sourceCode":"      const tidyBtn = document.getElementById('doclib-chats-tidy-btn');\n      const origHTML = tidyBtn.innerHTML;\n      tidyBtn.disabled = true;\n      tidyBtn.classList.add('spinning');\n      tidyBtn.textContent = '';\n      // Silent whirlpool, nudged up to line up with the surrounding button\n      // text in the Chats header. The previous version checked\n      // `window.spinnerModule` (never bound) and always fell through to a\n      // plain \"Tidying...\" label.\n      const sp = spinnerModule.create('', 'clean', 'whirlpool');\n      const el = sp.createElement();\n      el.style.position = 'relative';\n      el.style.top = '1px';\n      tidyBtn.appendChild(el);\n      sp.start();\n      try {\n        const res = await fetch(API_BASE + '/api/sessions/auto-sort', { method: 'POST', credentials: 'same-origin' });\n        const data = await res.json();\n        if (!res.ok) throw new Error(data.detail || 'Tidy failed');\n        if (data.status === 'ok') {\n          if (window.uiModule) window.uiModule.showToast('Sorted ' + data.updated + ' sessions into ' + data.folders.length + ' folders');\n          if (window.sessionModule) await window.sessionModule.loadSessions();\n          _renderLibChats();\n        } else {\n          if (window.uiModule) window.uiModule.showToast(data.reason || 'Nothing to tidy');\n        }\n      } catch (e) {\n        if (window.uiModule) window.uiModule.showError('Tidy: ' + e.message);\n      } finally {\n        tidyBtn.disabled = false;\n        tidyBtn.classList.remove('spinning');\n        tidyBtn.innerHTML = origHTML;\n      }\n    });\n\n    // ── Archive tab state ──\n    let _arcSessions = [];","sourceCodeStart":2275,"sourceCodeEnd":2311,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/documentLibrary.js#L2275-L2311","documentation":"Thrown in the Chats 'Tidy' (auto-sort) handler of documentLibrary.js when POST /api/sessions/auto-sort returns non-2xx or a body lacking detail. The message prefers data.detail and falls back to 'Tidy failed'; the catch shows 'Tidy: <message>'. Note res.json() runs before the ok check, so an HTML error page throws a SyntaxError instead, surfacing as 'Tidy: Unexpected token <'.","triggerScenarios":"Clicking Tidy while the LLM-backed sorter endpoint is unavailable or times out (504 from a long-running sort), auth expired (401), backend without the auto-sort route (404), or a 500 HTML page causing the JSON parse to fail first.","commonSituations":"Auto-sort depends on a model/LLM service — outages or slow inference make this button the first place users notice; older backend builds lacking /api/sessions/auto-sort; proxies with 30–60 s timeouts shorter than the sort.","solutions":["Check the toast suffix — data.detail (or the SyntaxError text) tells you whether it's an HTTP failure or an HTML error body.","Verify the backend exposes POST /api/sessions/auto-sort and its dependency (sorter/model) is configured.","Increase the proxy timeout for this endpoint or make it async/job-based if sorts are slow.","Re-authenticate if 401."],"exampleFix":"// before\nconst res = await fetch(API_BASE + '/api/sessions/auto-sort', { method: 'POST', credentials: 'same-origin' });\nconst data = await res.json();\nif (!res.ok) throw new Error(data.detail || 'Tidy failed');\n\n// after\nconst res = await fetch(API_BASE + '/api/sessions/auto-sort', { method: 'POST', credentials: 'same-origin' });\nlet data = null;\ntry { data = await res.json(); } catch (_) {}\nif (!res.ok) throw new Error((data && (data.detail || data.error)) || `HTTP ${res.status}`);","handlingStrategy":"try-catch","validationCode":"if (tidyBtn.disabled) return; // prevent double-invoke while a sort is running\ntidyBtn.disabled = true;","typeGuard":"function isSortResult(data) {\n  return data != null && typeof data === 'object'\n    && typeof data.status === 'string';\n}","tryCatchPattern":"try {\n  const res = await fetch(API_BASE + '/api/sessions/auto-sort', { method: 'POST', credentials: 'same-origin' });\n  let data = null;\n  try { data = await res.json(); } catch (_) {} // HTML error pages must not mask the status\n  if (!res.ok) throw new Error((data && (data.detail || data.error)) || `HTTP ${res.status}`);\n  if (!isSortResult(data)) throw new Error('Unexpected sort response');\n} catch (e) {\n  if (window.uiModule) window.uiModule.showError('Tidy: ' + e.message);\n} finally {\n  tidyBtn.disabled = false;\n}","preventionTips":["Parse the body after checking res.ok (or guard the parse) so 500 HTML pages don't surface as SyntaxError.","Disable the Tidy button during a run to avoid concurrent sorts.","Give the LLM-backed sort endpoint a generous timeout or convert it to a job; sorts can outlast proxy timeouts."],"tags":["http","auto-sort","llm-dependency","json-parse","sessions"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}