{"record":{"id":"754190bb81595ded","repo":"666ghj/MiroFish","slug":"zep-batch-api-returned-no-batch-id","errorCode":null,"errorMessage":"Zep Batch API returned no batch_id","messagePattern":"Zep Batch API returned no batch_id","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"backend/app/services/graph_builder.py","lineNumber":453,"sourceCode":"        try:\n            batch = self.client.batch.create(\n                metadata={\n                    \"mirofish_operation_id\": operation_id,\n                    \"graph_id\": graph_id,\n                    \"chunk_count\": total_chunks,\n                }\n            )\n        except Exception as error:\n            if not is_retryable_zep_error(error):\n                raise\n            batch = self._find_batch_by_operation_id(graph_id, operation_id)\n            if batch is None:\n                raise RuntimeError(\n                    \"Zep batch creation is unconfirmed and no matching operation was found\"\n                ) from error\n        batch_id = getattr(batch, \"batch_id\", None)\n        if not batch_id:\n            raise RuntimeError(\"Zep Batch API returned no batch_id\")\n        if batch_created_callback:\n            batch_created_callback(batch_id, operation_id)\n\n        episode_uuids: List[str] = []\n        for i in range(0, total_chunks, batch_size):\n            batch_chunks = chunks[i:i + batch_size]\n            batch_num = i // batch_size + 1\n            total_batches = (total_chunks + batch_size - 1) // batch_size\n            \n            if progress_callback:\n                progress = (i + len(batch_chunks)) / total_chunks\n                progress_callback(\n                    t('progress.sendingBatch', current=batch_num, total=total_batches, chunks=len(batch_chunks)),\n                    progress\n                )\n            \n            items = [\n                BatchAddItem(","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/services/graph_builder.py#L435-L471","documentation":"RuntimeError in submit_document_batch: a batch object came back from create (or from reconcile) but getattr(batch, 'batch_id', None) is empty — the Zep Batch API response lacks the batch identifier this code depends on for every subsequent call (add items, process, get). Without a batch_id the operation cannot proceed and, importantly, the batch_created_callback(batch_id, ...) journaling would persist a useless identity, so it aborts first.","triggerScenarios":"Zep SDK version change renaming the field (e.g. id instead of batch_id) so getattr misses it; a degenerate response object (empty model) returned during a partial outage; the reconcile path matched a batch stub without an id.","commonSituations":"Upgrading the zep-python SDK to a version with a different response schema; server error pages deserialized into empty objects; mocking in tests that returns a batch without batch_id.","solutions":["Log the full batch object when this fires to see the actual response shape.","Pin or upgrade the Zep SDK to the version this service was written against, and re-check the batch-create response schema.","If the field was renamed, read it with a fallback: batch_id = getattr(batch, 'batch_id', None) or getattr(batch, 'id', None).","Because the batch may exist server-side, look it up by mirofish_operation_id in the Zep console and clean up or resume manually.","In tests, make mocked batches include a realistic batch_id."],"exampleFix":"# before\nbatch_id = getattr(batch, \"batch_id\", None)\nif not batch_id:\n    raise RuntimeError(\"Zep Batch API returned no batch_id\")\n\n# after - tolerate schema rename, keep the hard failure for true emptiness\nbatch_id = (\n    getattr(batch, \"batch_id\", None)\n    or getattr(batch, \"id\", None)\n)\nif not batch_id:\n    raise RuntimeError(\n        f\"Zep Batch API returned no batch_id (response type={type(batch).__name__}, \"\n        f\"fields={getattr(batch, 'model_fields', None) or vars(getattr(batch, '__dict__', {}))}\"\n    )","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"def has_batch_id(batch) -> bool:\n    return bool(getattr(batch, 'batch_id', None) or getattr(batch, 'id', None))","tryCatchPattern":"try:\n    submission = builder.submit_document_batch(graph_id, chunks, batch_size=bs)\nexcept RuntimeError as e:\n    if 'no batch_id' in str(e):\n        # batch may exist server-side; find it by mirofish_operation_id before any retry\n        logger.error('Batch create returned no id; reconcile by operation metadata: %s', str(e))\n        raise\n    raise","preventionTips":["Pin the Zep SDK version and re-verify the batch-create response schema after upgrades.","Log the raw batch object when the id is missing to catch schema renames immediately.","In tests, always give mocked batches a realistic batch_id.","If a batch was created without a captured id, reconcile by mirofish_operation_id metadata instead of re-creating."],"tags":["backend","python","zep","batch","schema-mismatch","sdk"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}