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
- Re-run AI fill after reloading the document to clear stale ids/conflicts
- Raise proxy client_max_body_size if content grew past the limit
- Avoid editing the same document from two tabs concurrently
- 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
- Retry once on transient statuses (409/503) since the expensive AI step already succeeded
- Keep the merged markdown in doc.content so a failed save doesn't lose the AI output
- Parse the error body for detail instead of trusting statusText
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
- res.statusText || String(res.status)
- t || res.statusText
- t || r.statusText
- err || res.statusText
- await _pdfResponseErrorMessage(res)
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/bd58c849e5f15590.
Report an issue: GitHub.