odysseus-dev/odysseus · error · Error

res.statusText || String(res.status)

Error message

res.statusText || String(res.status)

What it means

Error thrown during undo-save of a PDF-linked document: PUT /api/document/{docId} with the previous content returned non-OK. Only statusText or the numeric status is used — the body is ignored, so the reason is usually vague (and many proxies send empty statusText). The UI then sets the save pill to 'error' with the undo-failed message.

Source

Thrown at static/js/document.js:1061

    if (!prev) return false;
    _pdfUndoStackByDoc.set(docId, stack);
    if (_pdfPaneSaveTimer) {
      clearTimeout(_pdfPaneSaveTimer);
      _pdfPaneSaveTimer = null;
    }
    const doc = docs.get(docId);
    if (!doc) return false;
    doc.content = prev;
    const ta = document.getElementById('doc-editor-textarea');
    if (ta) ta.value = prev;
    _setPdfSaveStatus('saving');
    try {
      const res = await fetch(`${API_BASE}/api/document/${docId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content: prev }),
      });
      if (!res.ok) throw new Error(res.statusText || String(res.status));
      _setPdfSaveStatus('saved');
      _renderPdfPane();
      return true;
    } catch (e) {
      _setPdfSaveStatus('error', e.message || 'Undo failed');
      return true;
    }
  }

  // Active drop mode for the PDF toolbar — toolbar buttons set this; the
  // next click on a page consumes it. null means clicks do nothing.
  let _pdfDropMode = null;
  // Per-doc last-used line spacing for text annotations. Once the user picks
  // 1.6 for one box, every text box dropped after that defaults to 1.6.
  const _pdfLastLineHeight = new Map(); // docId -> number
  function _setPdfDropMode(mode) {
    _pdfDropMode = mode;
    const pane = document.getElementById('doc-pdf-view');

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. curl -i -X PUT the same JSON to see the status and body the code drops
  2. Reload the session/document list to resync docId state if 404/409
  3. Free server disk / check write permissions for 500s
  4. Raise proxy client_max_body_size if large content triggers 413

Example fix

// before
if (!res.ok) throw new Error(res.statusText || String(res.status));

// after
if (!res.ok) {
  const t = await res.text().catch(() => '');
  throw new Error(t || res.statusText || `HTTP ${res.status}`);
}
Defensive patterns

Strategy: fallback

Validate before calling

if (!docs.has(docId)) return false; // undo target already gone

Try / catch

try {
  const res = await fetch(`${API_BASE}/api/document/${docId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: prev }) });
  if (!res.ok) {
    const t = await res.text().catch(() => '');
    throw new Error(t || res.statusText || `HTTP ${res.status}`);
  }
  _setPdfSaveStatus('saved');
  _renderPdfPane();
  return true;
} catch (e) {
  _setPdfSaveStatus('error', e.message || 'Undo failed');
  return true; // keep local content rolled back even if persist failed
}

Prevention

When it happens

Trigger: PUT /api/document/{id} returns 404 (doc deleted elsewhere), 409 (optimistic-concurrency conflict), 413 (content grew beyond a body limit), 500 (storage write failure).

Common situations: Document deleted in another tab/session while this one held an old copy; disk full on the server; proxy body-size limit exceeded after pasting large content into the editor; empty statusText making the message just '500'.

Related errors


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