{"record":{"id":"35dddb2d4b54a5d7","repo":"odysseus-dev/odysseus","slug":"referenced-upload-is-no-longer-available-missing","errorCode":null,"errorMessage":"Referenced upload is no longer available: {missing_upload_id}","messagePattern":"Referenced upload is no longer available: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"core/session_manager.py","lineNumber":248,"sourceCode":"        db = SessionLocal()\n        try:\n            db_session = db.query(DbSession).filter(DbSession.id == session_id).first()\n            if db_session is None:\n                # A stream/tool callback can outlive a session delete. Do not\n                # create a chat_messages row with no parent session; also drop\n                # any stale cached session so later writes fail closed too.\n                self.sessions.pop(session_id, None)\n                logger.warning(\"Dropping message for deleted session %s\", session_id)\n                return\n\n            missing_upload_id = reserve_message_upload_references(\n                getattr(self, \"upload_handler\", None),\n                getattr(db_session, \"owner\", None),\n                message.content,\n                message.metadata,\n            )\n            if missing_upload_id:\n                raise ValueError(\n                    f\"Referenced upload is no longer available: {missing_upload_id}\"\n                )\n\n            msg_id = str(uuid.uuid4())\n            msg_time = datetime.utcnow()\n            if message.metadata is None:\n                message.metadata = {}\n            message.metadata.setdefault('timestamp', _message_timestamp_iso(msg_time))\n            # Multimodal content may contain provider data URLs for the live\n            # model call. Persist only readable text plus attachment references\n            # so chat_messages/FTS do not duplicate upload bytes.\n            _content = persistable_message_content(message.content, message.metadata)\n            db_message = DbChatMessage(\n                id=msg_id,\n                session_id=session_id,\n                role=message.role,\n                content=_content,\n                meta_data=json.dumps(message.metadata) if message.metadata else None,","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/core/session_manager.py#L230-L266","documentation":"ValueError raised when persisting a new chat message: reserve_message_upload_references walks the message content/metadata for upload attachment references and finds an upload id that no longer exists (or is not owned by the session owner). This fails the write closed so transcripts never reference deleted uploads.","triggerScenarios":"Sending/adding a message that references an upload id which was deleted by cleanup or another session, or an upload owned by a different user than db_session.owner. Raised inside add_message/persist before the row is inserted.","commonSituations":"Long-lived chat tab after uploads were pruned by retention/cleanup; upload deleted from another device; owner mismatch after account or session ownership changes; replaying old client state after server-side purge.","solutions":["Re-upload the file and send a new message referencing the fresh upload id","If cleanup is racing active chats, exclude recently-referenced uploads from retention rules","Verify the session owner matches the upload owner when sharing contexts across accounts","On the client, drop stale attachment references before retrying the send"],"exampleFix":"# before\nsession_manager.add_message(session_id, message)  # references deleted upload\n# after — prune dead references first\nalive = [u for u in message.metadata.get('uploads', []) if upload_handler.get(u['id'])]\nmessage.metadata['uploads'] = alive\nsession_manager.add_message(session_id, message)","handlingStrategy":"validation","validationCode":"upload_ids = [u.get('id') for u in (message.metadata or {}).get('uploads', [])]\nmissing = [i for i in upload_ids if not upload_handler.get(i)]\nif missing:\n    raise ClientVisibleError(f'Attachments no longer available: {missing}')","typeGuard":null,"tryCatchPattern":"try:\n    sm.add_message(session_id, message)\nexcept ValueError as e:\n    if 'no longer available' in str(e):\n        strip_dead_uploads(message)\n        sm.add_message(session_id, message)  # retry without stale refs\n    else:\n        raise","preventionTips":["Check upload ids exist before sending messages that reference them","Exclude recently-referenced uploads from retention cleanup","Handle the ValueError client-side by re-uploading, not by retrying unchanged"],"tags":["uploads","validation","session","data-integrity"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}