{"record":{"id":"8fee9ca3c23c27b4","repo":"666ghj/MiroFish","slug":"zep-batch-list-cursor-did-not-advance","errorCode":null,"errorMessage":"Zep batch list cursor did not advance","messagePattern":"Zep batch list cursor did not advance","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"backend/app/services/graph_builder.py","lineNumber":299,"sourceCode":"            cursor: int | None = None\n            seen_cursors: set[int] = set()\n            while True:\n                page = call_zep_read_with_retry(\n                    lambda: self.client.batch.list(limit=100, cursor=cursor),\n                    operation_name=f\"reconcile batch create {operation_id}\",\n                )\n                for batch in getattr(page, \"batches\", None) or []:\n                    metadata = getattr(batch, \"metadata\", None) or {}\n                    if (\n                        metadata.get(\"mirofish_operation_id\") == operation_id\n                        and metadata.get(\"graph_id\") == graph_id\n                    ):\n                        matches.append(batch)\n                next_cursor = getattr(page, \"next_cursor\", None)\n                if next_cursor is None:\n                    break\n                if next_cursor == cursor or next_cursor in seen_cursors:\n                    raise RuntimeError(\"Zep batch list cursor did not advance\")\n                seen_cursors.add(next_cursor)\n                cursor = next_cursor\n\n            if len(matches) > 1:\n                raise RuntimeError(\n                    f\"Multiple Zep batches match operation {operation_id}; refusing ambiguity\"\n                )\n            if matches:\n                return matches[0]\n            if attempt < max_attempts:\n                time.sleep(attempt)\n        return None\n    \n    def set_ontology(self, graph_id: str, ontology: Dict[str, Any]):\n        \"\"\"设置图谱本体（公开方法）\"\"\"\n        import warnings\n        from typing import Optional\n        from pydantic import Field","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/services/graph_builder.py#L281-L317","documentation":"RuntimeError in _find_batch_by_operation_id (graph_builder.py): while paginating Zep's batch list to reconcile a possibly-lost batch-create response, the returned next_cursor either equals the current cursor or a previously seen one. The service treats a non-advancing cursor as a protocol violation and aborts instead of looping forever, because continuing would re-scan the same page and could append duplicate matches.","triggerScenarios":"Zep Cloud's batch list endpoint returns the same next_cursor for consecutive pages (server bug or pagination quirk); a middleware/proxy caches the list response so every page looks identical; the page object shape changed so next_cursor parsing yields a stale value.","commonSituations":"Zep SDK/API version drift changing pagination semantics; large batch histories triggering cursor reuse; flaky network plus an aggressive retry layer replaying the same response.","solutions":["Retry the operation — transient pagination glitches usually clear; the deterministic operation_id makes a retry of the whole reconcile safe.","Upgrade (or pin) the Zep client SDK to a version whose list-batch pagination matches this code path.","Inspect the raw page payloads (log next_cursor per iteration) to confirm whether the server or the client is repeating pages.","If Zep consistently misbehaves, list all batches non-paginated (if supported) or filter server-side by metadata to avoid cursor logic.","Check for caching proxies between the backend and Zep Cloud and exclude the batch endpoint."],"exampleFix":"# before\nif next_cursor == cursor or next_cursor in seen_cursors:\n    raise RuntimeError(\"Zep batch list cursor did not advance\")\n\n# after - tolerate one repeat by re-requesting once, then fail with context\nif next_cursor == cursor or next_cursor in seen_cursors:\n    if not retried_this_page:\n        retried_this_page = True\n        continue  # one fresh request for the same cursor\n    raise RuntimeError(\n        f\"Zep batch list cursor did not advance (cursor={cursor!r}, \"\n        f\"seen={len(seen_cursors)} pages); aborting to avoid an infinite loop\"\n    )","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    batch = builder._find_batch_by_operation_id(graph_id, operation_id)\nexcept RuntimeError as e:\n    if 'cursor did not advance' in str(e):\n        time.sleep(2)\n        batch = builder._find_batch_by_operation_id(graph_id, operation_id)  # one bounded retry\n    else:\n        raise","preventionTips":["Pin the Zep SDK version so pagination semantics match the code.","Log each next_cursor during reconcile to detect pagination anomalies early.","Ensure no caching proxy sits in front of api.zep.ai batch-list calls.","Keep the seen_cursors loop guard intact — never 'fix' this error by removing the cycle check."],"tags":["backend","python","zep","pagination","infinite-loop-guard"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}