{"record":{"id":"9b3e63e4bd3a7285","repo":"odysseus-dev/odysseus","slug":"msg-id-and-content-are-required","errorCode":null,"errorMessage":"msg_id and content are required","messagePattern":"msg_id and content are required","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/history/history_routes.py","lineNumber":351,"sourceCode":"                return {\"status\": \"ok\", \"deleted\": deleted}\n            finally:\n                db.close()\n        except KeyError:\n            raise HTTPException(404, \"Session not found\")\n        except Exception as e:\n            logger.error(f\"Delete messages error {session_id}: {e}\")\n            raise HTTPException(500, str(e))\n\n    @router.post(\"/api/session/{session_id}/edit-message\")\n    async def edit_message(request: Request, session_id: str):\n        \"\"\"Edit the content of a message by its database ID.\"\"\"\n        _verify_session_owner(request, session_id)\n        try:\n            body = await request.json()\n            msg_id = body.get(\"msg_id\")\n            content = body.get(\"content\")\n            if not msg_id or content is None:\n                raise HTTPException(400, \"msg_id and content are required\")\n\n            _reserve_message_uploads(request, content)\n\n            session = session_manager.get_session(session_id)\n            db = SessionLocal()\n            try:\n                db_msg = db.query(DbChatMessage).filter(\n                    DbChatMessage.id == msg_id,\n                    DbChatMessage.session_id == session_id,\n                ).first()\n                if not db_msg:\n                    raise HTTPException(404, \"Message not found\")\n\n                db_msg.content = content\n                meta = {}\n                if db_msg.meta_data:\n                    try: meta = json.loads(db_msg.meta_data)\n                    except (json.JSONDecodeError, ValueError): pass","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/history/history_routes.py#L333-L369","documentation":"Validation failure in POST /api/session/{session_id}/edit-message: the JSON body must contain both msg_id and content. The guard `if not msg_id or content is None` rejects missing/null msg_id, and also any falsy msg_id (0, empty string), while content only has to be non-None (empty string is allowed).","triggerScenarios":"POSTing {} or {\"msg_id\": 5} or {\"content\": \"hi\"} to the edit-message endpoint; sending msg_id as an empty string; a frontend bug that sends the field under a different key (e.g. \"id\" instead of \"msg_id\").","commonSituations":"Frontend/backend contract drift after renaming the payload key; sending a DB id of 0 (rejected even though technically an id); sending content: null when the user cleared the input box.","solutions":["Send both fields: {\"msg_id\": <int>, \"content\": \"<new text>\"}","Check the client is not renaming keys — the exact names msg_id and content are required","Do not send the request at all when the edit box is empty-and-submitted; guard client-side for msg_id being truthy and content being non-null"],"exampleFix":"// before\nawait fetch(`/api/session/${sid}/edit-message`, {\n  method: 'POST',\n  body: JSON.stringify({ id: msgId, content })\n});\n// after\nif (!msgId || content === null || content === undefined) return;\nawait fetch(`/api/session/${sid}/edit-message`, {\n  method: 'POST',\n  body: JSON.stringify({ msg_id: msgId, content })\n});","handlingStrategy":"validation","validationCode":"function validEditPayload(b) { return Number.isInteger(b.msg_id) && b.msg_id > 0 && 'content' in b && b.content !== null && b.content !== undefined; }","typeGuard":"const isEditBody = (b) => !!b && typeof b === 'object' && 'msg_id' in b && 'content' in b && b.content !== null;","tryCatchPattern":null,"preventionTips":["Pin the payload contract with a shared type/schema","Disable the save button until msg_id is known and content is non-null","Remember msg_id=0 and '' are rejected by truthiness, only content may be empty"],"tags":["fastapi","http-400","validation","payload-contract"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}