odysseus-dev/odysseus · warning · HTTPException
Invalid message attachment metadata
Error message
Invalid message attachment metadata
What it means
HTTP 400 raised by _reserve_message_uploads in history_routes when reserve_message_upload_references raises TypeError or ValueError. In src/upload_handler.py this happens when: metadata is a JSON string that fails json.loads (ValueError/JSONDecodeError) or is not None/str/dict-shaped (ValueError 'message metadata must be a JSON object'), or when its structure breaks attachment_refs_from_metadata (TypeError).
Source
Thrown at routes/history/history_routes.py:119
def setup_history_routes(session_manager, upload_handler=None) -> APIRouter:
router = APIRouter(tags=["history"])
def _reserve_message_uploads(
request: Request,
content: Any,
metadata: Any = None,
) -> None:
try:
missing_id = reserve_message_upload_references(
upload_handler,
effective_user(request),
content,
metadata,
)
except (TypeError, ValueError) as exc:
raise HTTPException(400, "Invalid message attachment metadata") from exc
if missing_id:
raise HTTPException(
409,
f"Referenced upload is no longer available: {missing_id}",
)
def _db_history_entry(m: DbChatMessage) -> Dict[str, Any]:
entry = {"role": m.role, "content": _history_display_content(m.content)}
meta = {}
if m.meta_data:
try:
meta = json.loads(m.meta_data) or {}
except (json.JSONDecodeError, ValueError):
meta = {}
if m.timestamp and "timestamp" not in meta:
meta["timestamp"] = m.timestamp.isoformat() + "Z"
if meta:
entry["metadata"] = metaView on GitHub (pinned to f9235ebbf1)
Solutions
- Send metadata as a real JSON object (or omit it), never a JSON string with syntax errors
- If you must send a string, ensure it is valid JSON and encodes an object
- Shape attachments per the server's expected ref format: a list of objects each with an 'attachment_id' key
Example fix
// before (double-encoded / malformed)
metadata: JSON.stringify('{oops')
// after
metadata: { attachments: [{ attachment_id: 'abc123.png' }] } Defensive patterns
Strategy: type-guard
Validate before calling
import json
def valid_metadata(md) -> bool:
if md in (None, ''):
return True
if isinstance(md, str):
try:
md = json.loads(md)
except ValueError:
return False
if not isinstance(md, dict):
return False
refs = md.get('attachments', [])
return all(isinstance(r, dict) and isinstance(r.get('attachment_id'), str) for r in refs) Type guard
function isValidMetadata(m: unknown): boolean {
if (m === null || m === undefined || m === '') return true;
let obj = m;
if (typeof m === 'string') { try { obj = JSON.parse(m); } catch { return false; } }
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) return false;
const refs = (obj as any).attachments;
return !Array.isArray(refs) || refs.every((r: any) => r && typeof r === 'object' && typeof r.attachment_id === 'string');
} Prevention
- Send metadata as a native JSON object; never stringify it twice
- Lint client serializers to catch '[object Object]' artifacts from FormData paths
- Keep attachment ref objects shaped {attachment_id: string} and add a contract test
When it happens
Trigger: POST /api/session/{id}/message with metadata as invalid JSON text ('{oops'), metadata as a list or number, or attachment refs array containing non-object entries.
Common situations: Client JSON.stringify-ing metadata twice (double-encoded string with stray quotes); sending FormData where metadata arrives as '[object Object]'; schema drift between client attachment format and attachment_refs_from_metadata expectations.
Related errors
- await res.text()
- Referenced upload is no longer available: {missing_upload_id
- {key} must be an integer
- Invalid JSON
- Expected a JSON object
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/b6dfe7266e6b6880.
Report an issue: GitHub.