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

  1. Check the toast suffix — data.detail (or the SyntaxError text) tells you whether it's an HTTP failure or an HTML error body.
  2. Verify the backend exposes POST /api/sessions/auto-sort and its dependency (sorter/model) is configured.
  3. Increase the proxy timeout for this endpoint or make it async/job-based if sorts are slow.
  4. 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

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


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