{"record":{"id":"bd12af9a51418fb1","repo":"odysseus-dev/odysseus","slug":"memory-store-is-temporarily-unreadable-nothing-w","errorCode":null,"errorMessage":"Memory store is temporarily unreadable — nothing was imported.","messagePattern":"Memory store is temporarily unreadable — nothing was imported\\.","errorType":"http","errorClass":"HTTPException","httpStatus":503,"severity":"error","filePath":"routes/backup_routes.py","lineNumber":86,"sourceCode":"        try:\n            body = await request.json()\n        except Exception:\n            raise HTTPException(400, \"Invalid JSON\")\n\n        if not isinstance(body, dict):\n            raise HTTPException(400, \"Expected a JSON object\")\n\n        imported = []\n\n        # ── Memories ──\n        if \"memories\" in body and isinstance(body[\"memories\"], list):\n            # Strict load: importing on top of an unreadable store would write\n            # only the incoming rows back and drop everything already saved.\n            try:\n                existing = memory_manager.load_all_for_update()\n            except MemoryStoreUnreadable as e:\n                logger.error(\"Refusing to import memories: %s\", e)\n                raise HTTPException(\n                    503, \"Memory store is temporarily unreadable — nothing was imported.\"\n                )\n            # Dedup against THIS user's own memories only. Using every tenant's\n            # rows (load_all) meant a memory whose text matched any other\n            # user's was silently skipped, so the importing user lost their own\n            # data. The full store is still saved back below.\n            existing_texts = {e.get(\"text\", \"\").strip().lower()\n                              for e in existing if e.get(\"owner\") == user}\n            added = 0\n            for mem in body[\"memories\"]:\n                if not isinstance(mem, dict) or not mem.get(\"text\"):\n                    continue\n                if mem[\"text\"].strip().lower() in existing_texts:\n                    continue  # skip duplicates\n                # Assign owner when auth is enabled\n                if user and not mem.get(\"owner\"):\n                    mem[\"owner\"] = user\n                existing.append(mem)","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/backup_routes.py#L68-L104","documentation":"HTTP 503 raised by POST /api/import when memory_manager.load_all_for_update() raises MemoryStoreUnreadable. The import path deliberately refuses to merge into an unreadable store: a naive merge would write only the incoming rows and silently drop all existing memories. 503 signals a transient server-side store problem — nothing was imported.","triggerScenarios":"POST /api/import (with a 'memories' list) while the memory store file/database is locked, corrupt, or otherwise unreadable (MemoryStoreUnreadable). Concurrent write, disk issue, or a partially-written store file.","commonSituations":"Importing while another process holds the store; store file corrupted after a crash mid-write; permissions changed on the data directory; importing immediately after an unclean shutdown.","solutions":["Retry the import after the store becomes readable (check the server log line 'Refusing to import memories').","Resolve the underlying store issue: lock contention, file permissions, or corruption repair.","Keep the export file — nothing was merged, so a clean retry is safe.","Do not attempt to 'force' the import by clearing the store; that is exactly the data-loss path the guard prevents."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (e) {\n  if (e.status === 503) { scheduleRetry(seconds(30)); /* keep export file, nothing was merged */ }\n  else throw e;\n}","preventionTips":["Treat 503 as transient: back off and retry later, never restructure the payload.","Check server logs for 'Refusing to import memories' to confirm the store cause.","Avoid importing while other writes to the memory store are running.","Never delete the memory store to 'fix' an import — that is the data-loss scenario the guard blocks."],"tags":["http-503","memory-store","import","transient","data-safety"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}