odysseus-dev/odysseus · error · Error
No draft id returned
Error message
No draft id returned
What it means
Thrown in the signed-email-reply flow of static/js/document.js after POST /api/document returns but the JSON contains neither id nor doc_id. The created draft email document cannot be tracked without an id, so the whole reply-draft creation aborts with 'Couldn't create reply draft'.
Source
Thrown at static/js/document.js:9374
|| _lastSessionId
|| (sessionModule && sessionModule.getCurrentSessionId());
if (!sessionId) {
try { sessionId = await _autoCreateSession(); } catch (_) {}
}
const cRes = await fetch(`${API_BASE}/api/document`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify({
session_id: sessionId,
title: reply.subject || 'Signed reply',
language: 'email',
content,
}),
});
const created = await cRes.json();
draftId = created && (created.id || created.doc_id);
if (!draftId) throw new Error('No draft id returned');
} catch (e) {
console.error('Failed to create draft doc:', e);
if (uiModule) uiModule.showError("Couldn't create reply draft");
return;
}
// Tag the draft (in-memory only) with the thread message-id so future
// signed PDFs from the same email get appended to this same draft.
addDocToTabs({
id: draftId,
title: reply.subject || 'Signed reply',
language: 'email',
current_content: content,
version_count: 1,
}, doc.sessionId);
const draft = docs.get(draftId);
if (draft) {
draft._composeAtts = [att];View on GitHub (pinned to f9235ebbf1)
Solutions
- Inspect the actual response body for the POST /api/document call in the reply flow.
- Add a res.ok check before parsing so HTTP failures get an accurate message instead of 'No draft id returned'.
- Align on the response contract (id or doc_id at the top level) across this and the other create call sites.
Example fix
// before
const created = await cRes.json();
draftId = created && (created.id || created.doc_id);
if (!draftId) throw new Error('No draft id returned');
// after
if (!cRes.ok) throw new Error(`Draft create failed: HTTP ${cRes.status}`);
const created = await cRes.json();
draftId = created && (created.id || created.doc_id || created.data?.id);
if (!draftId) throw new Error(`No draft id returned (keys: ${Object.keys(created || {}).join(', ')})`); Defensive patterns
Strategy: validation
Validate before calling
if (!sessionId || !content) { if (uiModule) uiModule.showError('Missing session or content for reply draft'); return; } Type guard
function hasDraftId(created) {
return created != null && typeof created === 'object'
&& Boolean(created.id || created.doc_id);
} Try / catch
try {
if (!cRes.ok) throw new Error(`Draft create failed: HTTP ${cRes.status}`);
const created = await cRes.json();
if (!hasDraftId(created)) throw new Error('No draft id returned');
draftId = created.id || created.doc_id;
} catch (e) {
console.error('Failed to create draft doc:', e);
if (uiModule) uiModule.showError("Couldn't create reply draft");
return;
} Prevention
- Always check res.ok before parsing — this site skips it, so HTTP failures masquerade as missing ids.
- Share one create-response normalizer (id || doc_id || data?.id) across all call sites.
- Log the response body when the shape is unexpected to catch API drift early.
When it happens
Trigger: Backend create succeeds (2xx) but returns an unexpected shape — e.g. {status: 'created'} with the id nested under data; an auth layer returning 200 with an error body; a refactor renaming id. Note this site does not check res.ok at all, so an error response with no id fields also lands here.
Common situations: Frontend/backend version drift on the create endpoint's response schema; gateways that rewrite responses; backend returning validation errors as 200.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/bf1ad5f41b14946f.
Report an issue: GitHub.