{"record":{"id":"aa9b798d19c94006","repo":"bytedance/deer-flow","slug":"retrieval-scope-userid-must-be-a-string-or-null","errorCode":null,"errorMessage":"retrieval scope userId must be a string or null","messagePattern":"retrieval scope userId must be a string or null","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/retrieval.py","lineNumber":547,"sourceCode":"            \"jieba\": _jieba_available,\n            \"db_path\": self._db_path,\n        }\n\n    def close(self) -> None:\n        with self._lock:\n            self._conn.close()\n\n\ndef _scope_value(value: str | None) -> str:\n    \"\"\"Encode a typed scope value so ``None`` cannot collide with a user id.\"\"\"\n    return json.dumps(value, ensure_ascii=False, separators=(\",\", \":\"))\n\n\ndef _scope_key(scope: dict[str, str | None]) -> tuple[str, str]:\n    user_id = scope.get(\"userId\")\n    agent_name = scope.get(\"agentName\")\n    if user_id is not None and not isinstance(user_id, str):\n        raise ValueError(\"retrieval scope userId must be a string or null\")\n    if agent_name is not None and not isinstance(agent_name, str):\n        raise ValueError(\"retrieval scope agentName must be a string or null\")\n    return _scope_value(user_id), _scope_value(agent_name)\n\n\nclass FTS5RetrievalAdapter:\n    \"\"\"Scope-aware ``RetrievalPort`` adapter backed by one SQLite FTS5 DB.\n\n    The index is derived data. Canonical facts remain in Markdown and storage\n    notifications update only the addressed row. A deterministic composite\n    document id prevents equal fact ids in two user/agent scopes from\n    overwriting each other.\n    \"\"\"\n\n    def __init__(self, db_path: str | Path = \":memory:\") -> None:\n        self._engine = FTS5Retrieval(db_path)\n\n    @staticmethod","sourceCodeStart":529,"sourceCodeEnd":565,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/retrieval.py#L529-L565","documentation":"ValueError from _scope_key in deermem's FTS5 retrieval layer: scope dict's 'userId' is present but not a str and not None (e.g. an int). Scope values are JSON-encoded into composite keys so None cannot collide with a real id; non-string ids would break that encoding, so they are rejected up front.","triggerScenarios":"Passing scopes=[{\"userId\": 12345, ...}] to FTS5RetrievalAdapter search/upsert paths — any retrieval call whose scope userId is an int, bool, or other non-string type.","commonSituations":"User ids stored as integers upstream (numeric PKs) and passed through unconverted; a dataclass/Pydantic model with int user id serialized into the scope dict; tests using fixture ints as user ids.","solutions":["Convert ids to str when building the scope: {\"userId\": str(user_id)}.","Normalize at the boundary: one helper that renders scope dicts with stringified ids and use it everywhere.","Keep the storage/retrieval layer's canonical user id format consistent (always str) end-to-end."],"exampleFix":"# before\nscopes = [{\"userId\": user.id, \"agentName\": None}]  # int id\n\n# after\nscopes = [{\"userId\": str(user.id), \"agentName\": None}]","handlingStrategy":"type-guard","validationCode":"def norm_scope(scope: dict) -> dict:\n    uid, agent = scope.get('userId'), scope.get('agentName')\n    if uid is not None and not isinstance(uid, str): uid = str(uid)\n    if agent is not None and not isinstance(agent, str): agent = str(agent)\n    return {'userId': uid, 'agentName': agent}","typeGuard":"def is_valid_scope(scope) -> bool:\n    return (\n        isinstance(scope, dict)\n        and (scope.get('userId') is None or isinstance(scope['userId'], str))\n        and (scope.get('agentName') is None or isinstance(scope['agentName'], str))\n    )","tryCatchPattern":null,"preventionTips":["Stringify user ids once at the system boundary (auth layer), keep them str everywhere downstream.","Build scopes through one helper; never hand-assemble dicts at call sites.","Assert scope shape in tests for every scope producer."],"tags":["typing","retrieval","deermem","memory","scope"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}