{"record":{"id":"ec208c93f23c59cf","repo":"odysseus-dev/odysseus","slug":"content-is-required","errorCode":null,"errorMessage":"content is required","messagePattern":"content is required","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/history/history_routes.py","lineNumber":270,"sourceCode":"            keep_count = body.get(\"keep_count\", 0)\n            result = session_manager.truncate_messages(session_id, keep_count)\n            return {\"status\": \"ok\", \"kept\": keep_count, \"truncated\": result}\n        except KeyError:\n            raise HTTPException(404, \"Session not found\")\n        except Exception as e:\n            logger.error(f\"Truncate error {session_id}: {e}\")\n            raise HTTPException(500, str(e))\n\n    @router.post(\"/api/session/{session_id}/message\")\n    async def add_message(request: Request, session_id: str):\n        \"\"\"Add a message to a session (for slash command persistence).\"\"\"\n        _verify_session_owner(request, session_id)\n        try:\n            body = await request.json()\n            role = body.get(\"role\", \"assistant\")\n            content = body.get(\"content\", \"\")\n            if not content:\n                raise HTTPException(400, \"content is required\")\n            metadata = body.get(\"metadata\")\n            _reserve_message_uploads(request, content, metadata)\n            msg = ChatMessage(role=role, content=content, metadata=metadata)\n            session_manager.add_message(session_id, msg)\n            return {\"status\": \"ok\"}\n        except KeyError:\n            raise HTTPException(404, \"Session not found\")\n\n    @router.post(\"/api/session/{session_id}/delete-messages\")\n    async def delete_messages(request: Request, session_id: str):\n        \"\"\"Delete specific messages by DB ID (or legacy index).\"\"\"\n        _verify_session_owner(request, session_id)\n        try:\n            body = await request.json()\n            msg_ids = body.get(\"msg_ids\", [])\n            indices = body.get(\"indices\")  # legacy fallback\n\n            session = session_manager.get_session(session_id)","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/history/history_routes.py#L252-L288","documentation":"HTTP 400 from POST /api/session/{session_id}/message: the body's 'content' field must be a non-empty string after defaulting to ''. Empty message text is rejected before upload-reference reservation or ChatMessage creation. role defaults to 'assistant' and is not validated here.","triggerScenarios":"POST with {\"role\": \"user\"} (no content), {\"content\": \"\"}, or {\"content\": null}; slash-command persistence layer firing with an empty command result.","commonSituations":"UI allowing send of whitespace-only or aborted messages; slash command producing empty output being persisted anyway; script echoing empty strings for assistant turns.","solutions":["Include non-empty content: {\"role\": \"user\", \"content\": \"hello\"}","Skip the API call entirely when the message body is empty (guard client-side)","If persisting slash-command output, check the result is non-empty before posting"],"exampleFix":"# before\nif True: post(f'/api/session/{sid}/message', json={'role':'user','content': text})\n\n# after\nif text and text.strip():\n    post(f'/api/session/{sid}/message', json={'role': 'user', 'content': text})","handlingStrategy":"validation","validationCode":"def valid_message_body(body: dict) -> bool:\n    content = body.get('content', '')\n    return isinstance(content, str) and bool(content.strip())","typeGuard":"function isValidMessage(b: unknown): b is { role: string; content: string } {\n  return typeof (b as any)?.content === 'string' && (b as any).content.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Skip the API call for empty/whitespace content — it can never succeed","Guard slash-command persistence to fire only when command output is non-empty","Trim content client-side and disable send on empty input"],"tags":["http-400","validation","session","message","request-body"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}