odysseus-dev/odysseus · error · HTTPException
str(e)
Error message
str(e)
What it means
HTTP 500 from truncate_session's generic except: any non-KeyError exception while parsing the body or executing truncate_messages is logged as 'Truncate error {session_id}' and returned verbatim as str(e). The message content depends entirely on the underlying exception — inspect logs to identify whether it was JSON parsing, a bad keep_count type, or a session-manager internal failure.
Source
Thrown at routes/history/history_routes.py:259
"history": history_dict,
"model": session.model,
"endpoint_url": session.endpoint_url,
"name": session.name,
}
@router.post("/api/session/{session_id}/truncate")
async def truncate_session(request: Request, session_id: str):
_verify_session_owner(request, session_id)
try:
body = await request.json()
keep_count = body.get("keep_count", 0)
result = session_manager.truncate_messages(session_id, keep_count)
return {"status": "ok", "kept": keep_count, "truncated": result}
except KeyError:
raise HTTPException(404, "Session not found")
except Exception as e:
logger.error(f"Truncate error {session_id}: {e}")
raise HTTPException(500, str(e))
@router.post("/api/session/{session_id}/message")
async def add_message(request: Request, session_id: str):
"""Add a message to a session (for slash command persistence)."""
_verify_session_owner(request, session_id)
try:
body = await request.json()
role = body.get("role", "assistant")
content = body.get("content", "")
if not content:
raise HTTPException(400, "content is required")
metadata = body.get("metadata")
_reserve_message_uploads(request, content, metadata)
msg = ChatMessage(role=role, content=content, metadata=metadata)
session_manager.add_message(session_id, msg)
return {"status": "ok"}
except KeyError:
raise HTTPException(404, "Session not found")View on GitHub (pinned to f9235ebbf1)
Solutions
- Read the server log line 'Truncate error <session_id>:' for the root cause
- Ensure the request sends Content-Type: application/json with {"keep_count": <int>}
- Serialize concurrent truncate/message calls to one session client-side
Example fix
# before
requests.post(url, data='{keep_count: 2}') # not JSON
# after
requests.post(url, json={"keep_count": 2}) Defensive patterns
Strategy: try-catch
Validate before calling
import json
def valid_truncate_body(body) -> bool:
try:
return isinstance(json.loads(json.dumps(body)), dict) \
and isinstance(body.get('keep_count', 0), int)
except (TypeError, ValueError):
return False Try / catch
try:
resp = requests.post(f'{base}/api/session/{sid}/truncate', json={'keep_count': 2}, timeout=30)
resp.raise_for_status()
except requests.HTTPError as e:
if e.response.status_code == 500:
log_server_pairing(f'Truncate error {sid}', e.response.text) # correlate with server log
raise Prevention
- Always send Content-Type: application/json (use the json= parameter of your HTTP client)
- Send keep_count as an integer, never null or a string
- Serialize session mutations so concurrent truncate/message calls don't race
When it happens
Trigger: Non-JSON request body (await request.json() raises), keep_count of an unexpected type that breaks truncate_messages, or an internal state mutation error in the session manager.
Common situations: Client sending text/plain bodies; proxies rewriting POST bodies; concurrency where two truncates race on the same session; keep_count sent as null.
Related errors
- Cleanup preview generation failed
- Created document not found
- SAM mask failed: {exc}
- Session not found
- content is required
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/0f6662cb09678cb8.
Report an issue: GitHub.