odysseus-dev/odysseus · error · Error

Document save failed: HTTP ${res.status}

Error message

Document save failed: HTTP ${res.status}

What it means

Thrown by saveDocument in static/js/document.js when the document save request (PUT/POST to the document endpoint) returns non-2xx — but only after the 404 branch above it has already handled deleted documents (dropping the tab and showing 'Document no longer exists'). So this throw means the save genuinely failed with a non-404 error.

Source

Thrown at static/js/document.js:9439

        }),
      });
      if (res.status === 404) {
        if (silent && localDoc?.language === 'email') {
          return;
        }
        // Streaming/empty email drafts can leave a local tab pointing at a temp
        // or already-deleted document. Do not keep surfacing autosave errors for
        // a document the backend no longer knows about.
        if (docs.has(savingDocId)) docs.delete(savingDocId);
        if (activeDocId === savingDocId) {
          activeDocId = null;
          renderTabs();
        }
        _syncDocIndicator();
        if (!silent && uiModule) uiModule.showError('Document no longer exists');
        return;
      }
      if (!res.ok) throw new Error(`Document save failed: HTTP ${res.status}`);
      const doc = await res.json();
      const badge = document.getElementById('doc-version-badge');
      if (badge) { const _v = doc.version_count || 1; badge.textContent = `v${_v}`; badge.style.display = _v > 1 ? '' : 'none'; }
      // Update map
      if (docs.has(savingDocId)) {
        docs.get(savingDocId).version = doc.version_count || 1;
        docs.get(savingDocId).content = contentToSave;
      }
      _syncDocIndicator();
      if (!silent && uiModule) uiModule.showToast(forceVersion ? 'New version saved' : 'Document saved');
    } catch (e) {
      console.error('Failed to save document:', e);
      const now = Date.now();
      if (uiModule && (!silent || now - _lastAutoSaveErrorAt > 10000)) {
        uiModule.showError(silent ? 'Autosave failed' : 'Failed to save document');
        _lastAutoSaveErrorAt = now;
      }
    }

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Identify the status: 413 → raise payload limit or trim content; 401 → re-login; 409 → reload the doc and merge; 5xx → server logs.
  2. If autosaves spam this error, the debounce (800 ms) plus silent flag hides it — watch the Network tab during typing.
  3. For 404-adjacent ghost tabs the code already self-heals; for other statuses verify the doc still exists in the Library.

Example fix

// before
if (!res.ok) throw new Error(`Document save failed: HTTP ${res.status}`);

// after
if (!res.ok) {
  let detail = '';
  try { const j = await res.json(); detail = j?.detail || j?.error || ''; } catch (_) {}
  throw new Error(`Document save failed: HTTP ${res.status}${detail ? ` — ${detail}` : ''}`);
}
Defensive patterns

Strategy: retry

Validate before calling

const doc = docs.get(savingDocId);
if (!doc) return; // nothing to save
if (savingDocId == null || contentToSave == null) return;

Type guard

function isSavedDoc(doc) {
  return doc != null && typeof doc === 'object'
    && doc.id != null
    && (doc.version_count == null || typeof doc.version_count === 'number');
}

Try / catch

let lastErr = null;
for (let attempt = 0; attempt < 2; attempt++) {
  try {
    const res = await doSave();
    if (res.status === 404) { dropGhostTab(savingDocId); return; }
    if (!res.ok) throw new Error(`Document save failed: HTTP ${res.status}`);
    const doc = await res.json();
    if (!isSavedDoc(doc)) throw new Error('Invalid save response');
    return;
  } catch (e) { lastErr = e; }
}
if (!silent && uiModule) uiModule.showError(`Save failed: ${lastErr.message}`);

Prevention

When it happens

Trigger: Autosave or manual save hitting 400/422 (content too large, invalid payload), 401/403 (expired session), 409 (version conflict), or 500 (DB write failure). 404 never reaches here — it is intercepted earlier to clean up ghost tabs.

Common situations: Very large email drafts exceeding a body-size limit; server restart losing sessions; concurrent edits producing conflicts; DB locked during backup.

Related errors


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