odysseus-dev/odysseus · warning · Error
Tidy failed
Error message
Tidy failed
What it means
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 <'.
Source
Thrown at static/js/documentLibrary.js:2293
const tidyBtn = document.getElementById('doclib-chats-tidy-btn');
const origHTML = tidyBtn.innerHTML;
tidyBtn.disabled = true;
tidyBtn.classList.add('spinning');
tidyBtn.textContent = '';
// Silent whirlpool, nudged up to line up with the surrounding button
// text in the Chats header. The previous version checked
// `window.spinnerModule` (never bound) and always fell through to a
// plain "Tidying..." label.
const sp = spinnerModule.create('', 'clean', 'whirlpool');
const el = sp.createElement();
el.style.position = 'relative';
el.style.top = '1px';
tidyBtn.appendChild(el);
sp.start();
try {
const res = await fetch(API_BASE + '/api/sessions/auto-sort', { method: 'POST', credentials: 'same-origin' });
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Tidy failed');
if (data.status === 'ok') {
if (window.uiModule) window.uiModule.showToast('Sorted ' + data.updated + ' sessions into ' + data.folders.length + ' folders');
if (window.sessionModule) await window.sessionModule.loadSessions();
_renderLibChats();
} else {
if (window.uiModule) window.uiModule.showToast(data.reason || 'Nothing to tidy');
}
} catch (e) {
if (window.uiModule) window.uiModule.showError('Tidy: ' + e.message);
} finally {
tidyBtn.disabled = false;
tidyBtn.classList.remove('spinning');
tidyBtn.innerHTML = origHTML;
}
});
// ── Archive tab state ──
let _arcSessions = [];View on GitHub (pinned to f9235ebbf1)
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.
Example fix
// before
const res = await fetch(API_BASE + '/api/sessions/auto-sort', { method: 'POST', credentials: 'same-origin' });
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Tidy failed');
// after
const res = await fetch(API_BASE + '/api/sessions/auto-sort', { method: 'POST', credentials: 'same-origin' });
let data = null;
try { data = await res.json(); } catch (_) {}
if (!res.ok) throw new Error((data && (data.detail || data.error)) || `HTTP ${res.status}`); Defensive patterns
Strategy: try-catch
Validate before calling
if (tidyBtn.disabled) return; // prevent double-invoke while a sort is running tidyBtn.disabled = true;
Type guard
function isSortResult(data) {
return data != null && typeof data === 'object'
&& typeof data.status === 'string';
} Try / catch
try {
const res = await fetch(API_BASE + '/api/sessions/auto-sort', { method: 'POST', credentials: 'same-origin' });
let data = null;
try { data = await res.json(); } catch (_) {} // HTML error pages must not mask the status
if (!res.ok) throw new Error((data && (data.detail || data.error)) || `HTTP ${res.status}`);
if (!isSortResult(data)) throw new Error('Unexpected sort response');
} catch (e) {
if (window.uiModule) window.uiModule.showError('Tidy: ' + e.message);
} finally {
tidyBtn.disabled = false;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to archive session
- data?.error || data?.detail || `HTTP ${res.status}`
- (data && (data.error || data.detail)) || `HTTP ${res.status}
- Failed
- Document create failed: HTTP ${res.status}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/32923a88287095cc.
Report an issue: GitHub.