{"record":{"id":"1c3acc1818435629","repo":"bytedance/deer-flow","slug":"openviking-capture-cursor-is-invalid-refusing-uns","errorCode":null,"errorMessage":"OpenViking capture cursor is invalid; refusing unsafe replay (session={session_id})","messagePattern":"OpenViking capture cursor is invalid; refusing unsafe replay \\(session=(.+?)\\)","errorType":"exception","errorClass":"MemoryManagerError","httpStatus":null,"severity":"error","filePath":"backend/packages/harness/deerflow/agents/memory/backends/openviking/openviking_manager.py","lineNumber":509,"sourceCode":"            if self._resources_closed:\n                return\n            self._recorder.close()\n            self._resources_closed = True\n\n    def _state_path(self, session_id: str) -> Path:\n        root = Path(self._config.storage_path or \".\") / \"openviking\" / \"sessions\"\n        return root / f\"{session_id}.json\"\n\n    def _load_cursor(self, session_id: str) -> dict[str, Any]:\n        path = self._state_path(session_id)\n        try:\n            value = json.loads(path.read_text(encoding=\"utf-8\"))\n        except FileNotFoundError:\n            return {}\n        except (OSError, ValueError) as exc:\n            raise MemoryManagerError(f\"OpenViking capture cursor is unreadable; refusing unsafe replay (session={session_id})\") from exc\n        if not isinstance(value, dict):\n            raise MemoryManagerError(f\"OpenViking capture cursor is invalid; refusing unsafe replay (session={session_id})\")\n        return value\n\n    def _save_cursor(self, session_id: str, state: dict[str, Any]) -> None:\n        path = self._state_path(session_id)\n        path.parent.mkdir(parents=True, exist_ok=True)\n        temp_path = path.with_suffix(f\".{os.getpid()}.{threading.get_ident()}.tmp\")\n        try:\n            temp_path.write_text(\n                json.dumps(state, ensure_ascii=False, indent=2),\n                encoding=\"utf-8\",\n            )\n            os.replace(temp_path, path)\n        finally:\n            try:\n                temp_path.unlink(missing_ok=True)\n            except OSError:\n                logger.debug(\n                    \"Failed to remove OpenViking cursor temp file: %s\",","sourceCodeStart":491,"sourceCodeEnd":527,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/packages/harness/deerflow/agents/memory/backends/openviking/openviking_manager.py#L491-L527","documentation":"_load_cursor() raises MemoryManagerError when the per-session cursor file at {storage_path}/openviking/sessions/{session_id}.json parses as valid JSON but is not an object (e.g. a JSON list, string, or number). The cursor must be a dict holding the suffix position and seen-message-ID set; any other top-level type means the state format is wrong, and replaying from it is unsafe, so the backend refuses.","triggerScenarios":"Someone or some tool replaced the cursor file with e.g. '[1,2,3]' or '\"done\"' — valid JSON, wrong shape. Distinct from error 630, which covers unreadable/unparseable content; this fires after successful json.loads when isinstance(value, dict) is False.","commonSituations":"Hand-editing session files; a downgrade/upgraded schema mismatch where an external tool wrote a different format into the same path.","solutions":["Delete or restore {storage_path}/openviking/sessions/{session_id}.json — a missing file yields {} and the session resumes with a fresh cursor","Do not write external tooling output into the openviking/sessions directory; it is private backend state","With write_failure_policy: log_and_drop (default) the failure is logged and the conversation continues; with 'raise' it becomes MemoryManagerError"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import json, pathlib\ndef cursor_well_formed(session_id: str, storage_path: str) -> bool:\n    p = pathlib.Path(storage_path or \".\", \"openviking\", \"sessions\", f\"{session_id}.json\")\n    if not p.exists():\n        return True\n    try:\n        return isinstance(json.loads(p.read_text(encoding=\"utf-8\")), dict)\n    except (OSError, ValueError):\n        return False","typeGuard":null,"tryCatchPattern":"from deerflow.agents.memory.manager import MemoryManagerError\ntry:\n    manager.add(thread_id, messages, user_id=user)\nexcept MemoryManagerError as exc:\n    if \"cursor is invalid\" in str(exc):\n        reset_cursor_file(session_id)  # remove non-dict JSON so the next write starts fresh\n    else:\n        raise","preventionTips":["Never let external tooling write into {storage_path}/openviking/sessions/","Treat the sessions directory as private backend state owned by the OpenViking adapter"],"tags":["openviking","memory","corruption","state-file"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}