odysseus-dev/odysseus · error · HTTPException

Message not found

Error message

Message not found

What it means

Raised by edit-message when the SQLAlchemy query filtering DbChatMessage.id == msg_id AND DbChatMessage.session_id == session_id returns no row. The message id must belong to the same session — a valid id from a different session also 404s (this doubles as an authorization boundary).

Source

Thrown at routes/history/history_routes.py:363

        _verify_session_owner(request, session_id)
        try:
            body = await request.json()
            msg_id = body.get("msg_id")
            content = body.get("content")
            if not msg_id or content is None:
                raise HTTPException(400, "msg_id and content are required")

            _reserve_message_uploads(request, content)

            session = session_manager.get_session(session_id)
            db = SessionLocal()
            try:
                db_msg = db.query(DbChatMessage).filter(
                    DbChatMessage.id == msg_id,
                    DbChatMessage.session_id == session_id,
                ).first()
                if not db_msg:
                    raise HTTPException(404, "Message not found")

                db_msg.content = content
                meta = {}
                if db_msg.meta_data:
                    try: meta = json.loads(db_msg.meta_data)
                    except (json.JSONDecodeError, ValueError): pass
                meta['edited'] = True
                db_msg.meta_data = json.dumps(meta)

                # Update in-memory history by matching _db_id
                for hmsg in session.history:
                    hmeta = hmsg.metadata if isinstance(hmsg, ChatMessage) else hmsg.get('metadata')
                    if isinstance(hmeta, dict) and hmeta.get('_db_id') == msg_id:
                        if isinstance(hmsg, ChatMessage):
                            hmsg.content = content
                            hmsg.metadata['edited'] = True
                        elif isinstance(hmsg, dict):
                            hmsg['content'] = content

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Re-fetch the session history (GET /api/session/{id}) and use a currently listed message's _db_id
  2. Verify the row exists and belongs to the session: SELECT id FROM chat_messages WHERE id = ? AND session_id = ?
  3. Ensure msg_id is sent as the numeric DB id, not the client-side array index or an id from a different/forked session
  4. If edits keep 404ing right after streaming, wait for the assistant message to be persisted before showing the edit affordance

Example fix

// before
const res = await fetch(`/api/session/${sid}/edit-message`, {method:'POST', body: JSON.stringify({msg_id: oldDbId, content})});
// after
const hist = await (await fetch(`/api/session/${sid}`)).json();
const live = hist.messages.find(m => m._db_id === oldDbId);
if (!live) { /* message was deleted/merged; refresh UI */ return; }
const res = await fetch(`/api/session/${sid}/edit-message`, {method:'POST', body: JSON.stringify({msg_id: live._db_id, content})});
Defensive patterns

Strategy: validation

Validate before calling

const hist = await (await fetch(`/api/session/${sid}`)).json();
const live = hist.messages?.some(m => m._db_id === msgId);
if (!live) { refreshTranscript(); return; }

Type guard

const isLiveMessage = (m, msgId, sid) => m && m._db_id === msgId && m.session_id === sid;

Try / catch

try { await editMessage(sid, msgId, content); }
catch (e) { if (e.status === 404) { await reloadHistory(sid); } else throw e; }

Prevention

When it happens

Trigger: Editing a message id from another session; editing a message whose DB row was already deleted (the delete-messages endpoint ran first in another tab); a message that exists only in memory because _persist_message failed silently; msg_id sent as a string when the column is an integer causing a type-mismatched filter.

Common situations: UI holds stale message ids after a fork/merge operation rewrote rows; two tabs editing the same conversation; message rows deleted by merge-last-assistant's _merge_continue_rows_to_delete while an edit dialog was open.

Related errors


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