{"record":{"id":"bbf3ac17f13e307b","repo":"666ghj/MiroFish","slug":"zep-batch-batch-id-item-cursor-did-not-advance","errorCode":null,"errorMessage":"Zep batch {batch_id} item cursor did not advance","messagePattern":"Zep batch (.+?) item cursor did not advance","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"backend/app/services/graph_builder.py","lineNumber":600,"sourceCode":"    def _list_batch_items(self, batch_id: str) -> List[Any]:\n        items: List[Any] = []\n        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_items(\n                    batch_id=batch_id,\n                    limit=100,\n                    cursor=cursor,\n                ),\n                operation_name=f\"list batch items {batch_id}\",\n            )\n            items.extend(getattr(page, \"items\", None) or [])\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(f\"Zep batch {batch_id} item cursor did not advance\")\n            seen_cursors.add(next_cursor)\n            cursor = next_cursor\n        return items\n\n    def _reconcile_batch_item_count(\n        self,\n        batch_id: str,\n        expected_item_count: int,\n        *,\n        max_attempts: int = 3,\n    ) -> List[Any]:\n        \"\"\"Allow a short propagation window after an ambiguous add reply.\"\"\"\n\n        items: List[Any] = []\n        for attempt in range(1, max_attempts + 1):\n            items = self._list_batch_items(batch_id)\n            if len(items) >= expected_item_count:\n                return items","sourceCodeStart":582,"sourceCodeEnd":618,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/services/graph_builder.py#L582-L618","documentation":"Raised inside GraphBuilder._list_batch_items while paginating Zep batch items with client.batch.list_items(limit=100, cursor=...). The loop terminates when next_cursor is None; if the API ever returns a cursor equal to the current one or one already visited (seen_cursors), it raises RuntimeError to prevent an infinite pagination loop. This indicates a server-side pagination anomaly or an SDK/API version mismatch rather than a problem with the submitted data.","triggerScenarios":"Calling _list_batch_items on a batch with more than 100 items (multiple pages), where Zep returns a next_cursor that repeats a previously seen value. Can also happen if the batch's items are still being written and list_items returns an unstable cursor, or when a pinned zep-cloud SDK version speaks an older cursor contract than the server.","commonSituations":"Large batches (hundreds/thousands of items) read immediately after batch completion; Zep service-side pagination regressions; upgrading or downgrading the zep-cloud SDK without regenerating cursor handling; rarely, clock/inconsistency windows during eventual consistency.","solutions":["Retry the whole read after a short wait (30-60s) — transient cursor instability right after batch completion usually resolves.","Check the zep-cloud SDK version against Zep's current docs and upgrade if it is behind; cursor semantics changed across versions.","If persistent, capture batch_id, page cursors and open a Zep support ticket — a never-advancing cursor is a service defect.","As a defensive workaround in your own fork, re-issue list_items from scratch instead of trusting the repeated cursor."],"exampleFix":"// before\nitems = self._list_batch_items(batch_id)\n# after\nfor attempt in range(3):\n    try:\n        items = self._list_batch_items(batch_id)\n        break\n    except RuntimeError as e:\n        if 'cursor did not advance' not in str(e) or attempt == 2:\n            raise\n        time.sleep(30)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for attempt in range(3):\n    try:\n        items = builder._list_batch_items(batch_id)\n        break\n    except RuntimeError as e:\n        if 'cursor did not advance' not in str(e):\n            raise\n        if attempt == 2:\n            raise\n        time.sleep(30)  # give Zep's cursor time to stabilize","preventionTips":["Pin a current zep-cloud SDK version and upgrade deliberately.","Avoid listing very large batches in the instant after completion; wait a few seconds.","Log cursors per page so a repeating cursor is diagnosable from history."],"tags":["zep","pagination","cursor","batch-api","infinite-loop-guard"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}