odysseus-dev/odysseus · error · Error

Document create failed: missing id

Error message

Document create failed: missing id

What it means

Thrown in static/js/document.js when POST /api/document returns 2xx but the JSON body has no id (falsy doc or doc.id). It guards addDocToTabs/switchToDoc from receiving an unusable document record — a response-shape contract violation rather than an HTTP failure.

Source

Thrown at static/js/document.js:7046

    // If the panel was in empty-state, the user may type into the editor
    // during the create round-trip — preserve that text into the new doc
    // instead of letting switchToDoc blank it.
    const wasEmpty = !activeDocId;
    try {
      const res = await fetch(`${API_BASE}/api/document`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({
          session_id: sessionId,
          title: '',
          content: '',
          language: 'markdown',
        }),
      });
      if (!res.ok) throw new Error(`Document create failed: HTTP ${res.status}`);
      const doc = await res.json();
      if (!doc || !doc.id) throw new Error('Document create failed: missing id');
      addDocToTabs(doc, sessionId);
      if (!isOpen) openPanel();
      // Re-enable editor if it was in empty state
      let textarea = document.getElementById('doc-editor-textarea');
      if (textarea) {
        textarea.disabled = false;
        textarea.placeholder = 'Document content...';
      }
      // Capture text typed during the round-trip (only when starting from the
      // empty editor — don't steal another doc's content).
      const typed = (wasEmpty && textarea && textarea.value.trim()) ? textarea.value : '';
      switchToDoc(doc.id);
      if (typed) {
        textarea = document.getElementById('doc-editor-textarea');
        if (textarea) textarea.value = typed;
        const d = docs.get(doc.id);
        if (d) d.content = typed;
        syncHighlighting();

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Inspect the actual 200 response body for POST /api/document.
  2. If the field was renamed, accept the alias here like the import path does (doc.id || doc.doc_id) or fix the backend to return id.
  3. Ensure no response-transforming middleware wraps the payload.

Example fix

// before
const doc = await res.json();
if (!doc || !doc.id) throw new Error('Document create failed: missing id');

// after
const doc = await res.json();
const newId = doc && (doc.id || doc.doc_id);
if (!newId) throw new Error(`Document create failed: missing id (keys: ${Object.keys(doc || {}).join(', ')})`);
doc.id = newId;
Defensive patterns

Strategy: type-guard

Type guard

function hasDocumentId(doc) {
  return doc != null && typeof doc === 'object'
    && (typeof doc.id === 'string' || typeof doc.id === 'number')
    && String(doc.id).length > 0;
}

Try / catch

const doc = await res.json();
if (!hasDocumentId(doc) && !doc?.doc_id) {
  throw new Error(`Document create failed: missing id (got keys: ${Object.keys(doc || {}).join(', ')})`);
}

Prevention

When it happens

Trigger: Backend returns {ok: true} or an empty object instead of the created document; a proxy or interceptor rewrites the response; backend refactor renamed id to doc_id (note the sibling import path at document.js:9584 accepts both j.id || j.doc_id, so this site is stricter).

Common situations: API version drift where the create endpoint's response schema changed; middleware returning 200 with a wrapper object {data: {...}}.

Related errors


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