{"record":{"id":"bf1ad5f41b14946f","repo":"odysseus-dev/odysseus","slug":"no-draft-id-returned","errorCode":null,"errorMessage":"No draft id returned","messagePattern":"No draft id returned","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/document.js","lineNumber":9374,"sourceCode":"        || _lastSessionId\n        || (sessionModule && sessionModule.getCurrentSessionId());\n      if (!sessionId) {\n        try { sessionId = await _autoCreateSession(); } catch (_) {}\n      }\n      const cRes = await fetch(`${API_BASE}/api/document`, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        credentials: 'same-origin',\n        body: JSON.stringify({\n          session_id: sessionId,\n          title: reply.subject || 'Signed reply',\n          language: 'email',\n          content,\n        }),\n      });\n      const created = await cRes.json();\n      draftId = created && (created.id || created.doc_id);\n      if (!draftId) throw new Error('No draft id returned');\n    } catch (e) {\n      console.error('Failed to create draft doc:', e);\n      if (uiModule) uiModule.showError(\"Couldn't create reply draft\");\n      return;\n    }\n\n    // Tag the draft (in-memory only) with the thread message-id so future\n    // signed PDFs from the same email get appended to this same draft.\n    addDocToTabs({\n      id: draftId,\n      title: reply.subject || 'Signed reply',\n      language: 'email',\n      current_content: content,\n      version_count: 1,\n    }, doc.sessionId);\n    const draft = docs.get(draftId);\n    if (draft) {\n      draft._composeAtts = [att];","sourceCodeStart":9356,"sourceCodeEnd":9392,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/document.js#L9356-L9392","documentation":"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'.","triggerScenarios":"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.","commonSituations":"Frontend/backend version drift on the create endpoint's response schema; gateways that rewrite responses; backend returning validation errors as 200.","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."],"exampleFix":"// before\nconst created = await cRes.json();\ndraftId = created && (created.id || created.doc_id);\nif (!draftId) throw new Error('No draft id returned');\n\n// after\nif (!cRes.ok) throw new Error(`Draft create failed: HTTP ${cRes.status}`);\nconst created = await cRes.json();\ndraftId = created && (created.id || created.doc_id || created.data?.id);\nif (!draftId) throw new Error(`No draft id returned (keys: ${Object.keys(created || {}).join(', ')})`);","handlingStrategy":"validation","validationCode":"if (!sessionId || !content) { if (uiModule) uiModule.showError('Missing session or content for reply draft'); return; }","typeGuard":"function hasDraftId(created) {\n  return created != null && typeof created === 'object'\n    && Boolean(created.id || created.doc_id);\n}","tryCatchPattern":"try {\n  if (!cRes.ok) throw new Error(`Draft create failed: HTTP ${cRes.status}`);\n  const created = await cRes.json();\n  if (!hasDraftId(created)) throw new Error('No draft id returned');\n  draftId = created.id || created.doc_id;\n} catch (e) {\n  console.error('Failed to create draft doc:', e);\n  if (uiModule) uiModule.showError(\"Couldn't create reply draft\");\n  return;\n}","preventionTips":["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."],"tags":["api-contract","response-shape","email-reply","draft"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}