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

  1. Inspect the actual response body for the POST /api/document call in the reply flow.
  2. Add a res.ok check before parsing so HTTP failures get an accurate message instead of 'No draft id returned'.
  3. 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

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.