{"record":{"id":"d17f1404d816c582","repo":"odysseus-dev/odysseus","slug":"invalid-draft-payload-exc","errorCode":null,"errorMessage":"Invalid draft payload: {exc}","messagePattern":"Invalid draft payload: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/codex_routes.py","lineNumber":373,"sourceCode":"        )\n        result = await _as_owner(request, owner, documents_create_endpoint, request, req)\n        if isinstance(result, dict):\n            result = dict(result)\n            result[\"draft_type\"] = \"document\"\n            result[\"send_required_confirmation\"] = True\n        return result\n\n    @router.post(\"/emails/draft\")\n    async def codex_email_draft(request: Request, body: dict[str, Any] = Body(default_factory=dict)):\n        owner = _scope_owner(request, EMAIL_DRAFT_SCOPES)\n        if email_draft_endpoint is None:\n            raise HTTPException(503, \"Email integration is not available\")\n        from routes.email_routes import SendEmailRequest\n\n        try:\n            req = SendEmailRequest(**body)\n        except Exception as exc:\n            raise HTTPException(400, f\"Invalid draft payload: {exc}\")\n        return await email_draft_endpoint(req=req, owner=owner)\n\n    @router.post(\"/emails/send\")\n    async def codex_email_send(request: Request, body: dict[str, Any] = Body(default_factory=dict)):\n        owner = _scope_owner(request, EMAIL_SEND_SCOPES)\n        if email_send_endpoint is None:\n            raise HTTPException(503, \"Email integration is not available\")\n        from routes.email_routes import SendEmailRequest\n\n        try:\n            req = SendEmailRequest(**body)\n        except Exception as exc:\n            raise HTTPException(400, f\"Invalid send payload: {exc}\")\n        return await email_send_endpoint(req=req, background_tasks=BackgroundTasks(), owner=owner)\n\n    # ── Memory ────────────────────────────────────────────────────────────\n\n    @router.get(\"/memory\")","sourceCodeStart":355,"sourceCodeEnd":391,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/codex_routes.py#L355-L391","documentation":"Raised as HTTP 400 by POST /api/codex/emails/draft (and the send sibling) when the JSON body fails to construct routes.email_routes.SendEmailRequest — a pydantic-style request model. The exception text is appended verbatim, so the message enumerates exactly which fields failed validation (missing required fields, wrong types, unknown constraints).","triggerScenarios":"POSTing a body missing required fields (e.g. no recipient/subject/content depending on the model), sending strings where the model expects other types, extra fields rejected by the model config, or sending a raw string body instead of a JSON object.","commonSituations":"Codex plugin or script submitting a partial draft (only 'body' text); client omits Content-Type: application/json so the body parses as garbage; field renames between versions (to -> recipient) breaking older clients.","solutions":["Read the interpolated pydantic error in the message — it names the offending field(s); fix the payload accordingly.","Fetch/inspect SendEmailRequest's current field list (routes/email_routes.py) and send all required fields with correct types.","Ensure the request sends a JSON object with Content-Type: application/json.","After an upgrade, re-check the model: renamed or newly-required fields are the usual breakers."],"exampleFix":"# before\ncurl -X POST .../api/codex/emails/draft -d '{\"body\": \"hello\"}'  # 400 Invalid draft payload\n\n# after\ncurl -X POST .../api/codex/emails/draft -H 'Content-Type: application/json' \\\n  -d '{\"to\": \"a@b.com\", \"subject\": \"hi\", \"body\": \"hello\"}'","handlingStrategy":"validation","validationCode":"function validDraftPayload(b) {\n  return !!b && typeof b === 'object'\n    && typeof b.to === 'string' && b.to.includes('@')\n    && typeof b.subject === 'string'\n    && typeof b.body === 'string';\n}\nif (!validDraftPayload(body)) throw new Error('fix payload before sending');","typeGuard":"interface SendEmailRequest { to: string; subject: string; body: string }\nfunction isSendEmailRequest(v: unknown): v is SendEmailRequest {\n  const o = v as Record<string, unknown>;\n  return typeof o?.to === 'string' && typeof o?.subject === 'string' && typeof o?.body === 'string';\n}","tryCatchPattern":"try { r = await codexEmailDraft(body) } catch (e) { if (e.status === 400 && e.detail.startsWith('Invalid draft payload')) { showFieldErrors(e.detail) } else throw }","preventionTips":["Validate against the SendEmailRequest model shape client-side before POSTing.","Always send Content-Type: application/json with a JSON object body.","Re-read the model definition after upgrades — field renames/new required fields are the usual breakers."],"tags":["http-400","pydantic","validation","email","codex"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}