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"] = meta

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Send metadata as a real JSON object (or omit it), never a JSON string with syntax errors
  2. If you must send a string, ensure it is valid JSON and encodes an object
  3. 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

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


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/b6dfe7266e6b6880. Report an issue: GitHub.