odysseus-dev/odysseus · warning · HTTPException
Referenced upload is no longer available: {missing_id}
Error message
Referenced upload is no longer available: {missing_id} What it means
HTTP 409 Conflict from _reserve_message_uploads: message content or metadata referenced an internal upload ID that the upload handler could not reserve — the file is gone, expired, owned by someone else, or the ID is invalid. The response names the exact offending missing_id. This prevents recording messages pointing at dead attachments.
Source
Thrown at routes/history/history_routes.py:121
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
return entry
View on GitHub (pinned to f9235ebbf1)
Solutions
- Re-upload the attachment to get a fresh upload ID, then resend the message
- Strip the stale attachment_id from metadata/content if the reference is no longer needed
- If the file should still exist, verify it lies in the server's upload dir and is owned by the requesting user
Example fix
# client
resp = post(f'/api/session/{sid}/message', json=payload)
if resp.status_code == 409:
missing = resp.json().get('detail', '').rsplit(': ', 1)[-1]
payload['metadata']['attachments'] = [
a for a in payload['metadata'].get('attachments', [])
if a.get('attachment_id') != missing]
reupload_missing(); post(...) # or resend without it Defensive patterns
Strategy: fallback
Validate before calling
def referenced_uploads_available(metadata) -> bool:
ids = {a['attachment_id'] for a in (metadata or {}).get('attachments', []) if a.get('attachment_id')}
for uid in ids:
r = requests.head(f'{base}/api/uploads/{uid}', headers=hdrs, timeout=15)
if r.status_code != 200:
return False
return True Try / catch
try:
post_message(sid, payload)
except HTTPError as e:
if e.response.status_code == 409:
missing = e.response.json()['detail'].rsplit(': ', 1)[-1].strip()
payload = drop_reference(payload, missing) # fall back: send without it
post_message(sid, payload) # then re-upload + re-send if needed
else:
raise Prevention
- Upload attachments and send the referencing message promptly — uploads expire after the retention window (cleanup_days)
- After any upload failure or admin cleanup, purge matching attachment_ids from queued messages
- Never reference upload IDs obtained under a different account; reservation is owner-scoped
When it happens
Trigger: POST a message whose content embeds an internal upload reference after the underlying file was deleted or aged out (cleanup_days=30 pruning), or whose metadata.attachment_id references another user's upload (reserve_upload with allow_admin=False rejects cross-owner).
Common situations: Uploads older than the 30-day retention window; file removed by an admin/cleanup job; copying message payloads between accounts; attachment ID typo'd or truncated by rich-text serialization.
Related errors
- Referenced upload is no longer available: {missing_id}
- Username already taken
- Invalid message attachment metadata
- Referenced upload is no longer available: {missing_id}
- data?.error || data?.detail || `HTTP ${res.status}`
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/8f8896bc577ab63c.
Report an issue: GitHub.