{"record":{"id":"7b890e1605fe7616","repo":"666ghj/MiroFish","slug":"multiple-zep-batches-match-operation-operation-id","errorCode":null,"errorMessage":"Multiple Zep batches match operation {operation_id}; refusing ambiguity","messagePattern":"Multiple Zep batches match operation (.+?); refusing ambiguity","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"backend/app/services/graph_builder.py","lineNumber":304,"sourceCode":"                    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\n        from zep_cloud.external_clients.ontology import EntityModel, EntityText, EdgeModel\n        \n        # 抑制 Pydantic v2 关于 Field(default=None) 的警告\n        # 这是 Zep SDK 要求的用法，警告来自动态类创建，可以安全忽略\n        warnings.filterwarnings('ignore', category=UserWarning, module='pydantic')","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/services/graph_builder.py#L286-L322","documentation":"RuntimeError in _find_batch_by_operation_id: the paginated batch list found two or more Zep batches whose metadata carries the same mirofish_operation_id (and graph_id). Because a batch-create call is not idempotent, a replayed create after an ambiguous timeout can produce a duplicate batch; rather than guessing which one holds the canonical data, the service refuses and asks for manual disambiguation.","triggerScenarios":"A retryable error on client.batch.create triggers _find_batch_by_operation_id, and the list now shows the original batch plus a duplicate created by an earlier replay; two concurrent build submissions computed the same operation_id; leftover batches from a previous failed run with identical content (same graph_id + chunks hash to the same operation_id).","commonSituations":"Network flakiness causing the create to be issued twice; resuming a build after a crash where the previous batch was never cleaned up; running the same document against the same graph twice and the old batch was not deleted.","solutions":["Inspect all batches with this operation_id in the Zep Cloud console (metadata mirofish_operation_id) and compare item counts/status.","Delete the stale/draft duplicate batch in Zep, keep the processed one, then retry the build so the reconcile finds a single match.","If both are partial drafts, delete both and restart the build fresh for that operation.","Prevent recurrence: always clear project.zep_batch_id when abandoning a build, and never re-submit create for an operation whose batch may already exist.","If concurrent submissions are possible, serialize build submission per project (the graph_lifecycle_lock pattern) so the same operation_id is never in flight twice."],"exampleFix":"# before\nif len(matches) > 1:\n    raise RuntimeError(f\"Multiple Zep batches match operation {operation_id}; refusing ambiguity\")\n\n# after - prefer the processed batch deterministically, still refuse true ties\nif len(matches) > 1:\n    processed = [b for b in matches if getattr(b, \"status\", None) not in {None, \"draft\"}]\n    drafts = [b for b in matches if b not in processed]\n    if len(processed) == 1:\n        for d in drafts:\n            self._safe_delete_batch(getattr(d, \"batch_id\", None))\n        matches = processed\n    else:\n        raise RuntimeError(\n            f\"Multiple Zep batches match operation {operation_id} \"\n            f\"(ids={[getattr(b, 'batch_id', None) for b in matches]}); refusing ambiguity\"\n        )","handlingStrategy":"validation","validationCode":"matches = [b for b in list_all_batches() if (b.metadata or {}).get('mirofish_operation_id') == operation_id]\nif len(matches) > 1:\n    # operator must pick: prefer processed, delete drafts; do not auto-choose blindly\n    raise AmbiguousBatchError([b.batch_id for b in matches])","typeGuard":null,"tryCatchPattern":"try:\n    submission = builder.submit_document_batch(graph_id, chunks, batch_size=bs)\nexcept RuntimeError as e:\n    if 'refusing ambiguity' in str(e):\n        # inspect + clean duplicates in Zep console, then restart the build fresh\n        logger.error('Duplicate Zep batches detected: %s', str(e))\n        raise\n    raise","preventionTips":["Never re-POST batch.create for an operation whose create outcome is uncertain; always reconcile by metadata first.","Clear project.zep_batch_id/operation_id when abandoning a build so stale batches are not resumed.","Serialize build submissions per project so the same operation_id is never in flight twice.","Clean up draft batches in Zep after failed runs before starting new ones."],"tags":["backend","python","zep","batch","idempotency","ambiguity"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}