odysseus-dev/odysseus · error · Error

t || r2.statusText

Error message

t || r2.statusText

What it means

Error thrown in the second phase of AI fill: after annotations were proposed, persisting the merged markdown via PUT /api/document/{docId} returned non-OK. Body text or statusText becomes the message; the UI shows 'AI fill failed: ...' even though the AI part succeeded — only the save failed.

Source

Thrown at static/js/document.js:1838

          x: Math.max(0, Math.min(100, parseFloat(a.x) || 0)),
          y: Math.max(0, Math.min(100, parseFloat(a.y) || 0)),
          w: Math.max(0.5, Math.min(100, parseFloat(a.w) || 22)),
          h: Math.max(0.3, Math.min(100, parseFloat(a.h) || 3.5)),
          value: String(a.value || ''),
        });
      }
      const newMd = _writeAnnotations(doc.content || '', combined);
      doc.content = newMd;
      const ta = document.getElementById('doc-editor-textarea');
      if (ta) ta.value = newMd;
      const r2 = await fetch(`${API_BASE}/api/document/${docId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content: newMd }),
      });
      if (!r2.ok) {
        const t = await r2.text().catch(() => r2.statusText);
        throw new Error(t || r2.statusText);
      }
      _setPdfSaveStatus('saved');
      if (uiModule && uiModule.showToast) uiModule.showToast(`AI added ${proposed.length} annotations`);
      _renderPdfPane();
    } catch (e) {
      console.error('AI fill failed:', e);
      _setPdfSaveStatus('error', `AI fill failed: ${e.message || e}`);
    } finally {
      if (btn) { btn.disabled = false; btn.textContent = 'AI fill'; }
    }
  }

  function _schedulePdfPaneSave() {
    _setPdfSaveStatus('dirty');
    if (_pdfPaneSaveTimer) clearTimeout(_pdfPaneSaveTimer);
    _pdfPaneSaveTimer = setTimeout(() => _savePdfPaneToMarkdown(), 600);
  }

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Re-run AI fill after reloading the document to clear stale ids/conflicts
  2. Raise proxy client_max_body_size if content grew past the limit
  3. Avoid editing the same document from two tabs concurrently
  4. Check server disk/permissions for 500s
Defensive patterns

Strategy: retry

Validate before calling

if (!docs.has(docId)) { _setPdfSaveStatus('idle'); return; } // doc vanished mid-flow

Try / catch

try {
  const r2 = await fetch(`${API_BASE}/api/document/${docId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: newMd }) });
  if (!r2.ok && (r2.status === 409 || r2.status === 503)) {
    await new Promise(r => setTimeout(r, 800));
    const r3 = await fetch(`${API_BASE}/api/document/${docId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content: newMd }) });
    if (!r3.ok) throw new Error((await r3.text().catch(() => '')) || `HTTP ${r3.status}`);
  } else if (!r2.ok) {
    throw new Error((await r2.text().catch(() => '')) || r2.statusText || `HTTP ${r2.status}`);
  }
  _setPdfSaveStatus('saved');
} catch (e) {
  _setPdfSaveStatus('error', `AI fill failed: ${e.message || e}`);
}

Prevention

When it happens

Trigger: PUT /api/document/{id} returns 404 (doc deleted mid-flow), 409 (concurrent modification), 413 (annotation-inflated content exceeds a body size limit), 500 (storage failure).

Common situations: Another tab saved the same doc between preview and persist; large signature/annotation payloads crossing proxy limits; disk full; docId invalidated by a session switch during the async flow.

Related errors


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