{"record":{"id":"17030b9360a3ec1c","repo":"666ghj/MiroFish","slug":"graph-id-is-required","errorCode":null,"errorMessage":"graph_id is required","messagePattern":"graph_id is required","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/app/services/graph_builder.py","lineNumber":424,"sourceCode":"    \n    def add_text_batches(\n        self,\n        graph_id: str,\n        chunks: List[str],\n        batch_size: int = 350,\n        progress_callback: Optional[Callable] = None,\n        batch_created_callback: Optional[Callable[[str | None, str], None]] = None,\n    ) -> BatchSubmission:\n        \"\"\"Submit document chunks through Zep's current Batch API.\n\n        Mutating calls are deliberately not retried: create/add are not\n        documented as idempotent, and an ambiguous replay can duplicate graph\n        episodes. The returned batch identity allows callers to persist and\n        reconcile the operation instead.\n        \"\"\"\n\n        if not graph_id:\n            raise ValueError(\"graph_id is required\")\n        self.validate_batch_chunks(chunks, batch_size=batch_size)\n\n        total_chunks = len(chunks)\n        operation_id = self.build_operation_id(graph_id, chunks)\n        if batch_created_callback:\n            # Journal the deterministic operation before the server-generated\n            # batch ID POST. This leaves enough identity for later diagnosis\n            # even if both the response and immediate list reconciliation fail.\n            batch_created_callback(None, operation_id)\n\n        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            )","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/services/graph_builder.py#L406-L442","documentation":"ValueError in submit_document_batch (graph_builder.py): the method requires an existing Zep graph to attach batch episodes to, and an empty/None graph_id is rejected before any Cloud mutation. Graphs must be created first (create_graph) so the batch has a destination; this check deliberately fails before the batch_created_callback journaling fires, keeping state clean.","triggerScenarios":"Calling submit_document_batch with graph_id=None/'' because the caller skipped graph creation, the create call failed but the error was swallowed, or the project record's graph_id was cleared (e.g. by _clear_project_graph_reference) while a build was still being attempted.","commonSituations":"Race between graph deletion/rebuild and an in-flight document submission; caller assumes the service creates the graph implicitly; project row lost its graph_id after a failed delete flow.","solutions":["Create the graph first (builder.create_graph / the endpoint that provisions it) and pass the returned graph_id.","If resuming, verify project.graph_id is still set before calling submit; re-create the graph if it was cleared.","Guard concurrent delete/rebuild vs submit with the same per-graph lifecycle lock used elsewhere.","Add an assertion/log at the API layer so a None graph_id is caught with the project id in context."],"exampleFix":"# before\ndef submit_document_batch(self, graph_id, chunks, *, batch_size=350, ...):\n    if not graph_id:\n        raise ValueError(\"graph_id is required\")\n\n# after - fail with context naming the caller's project state\ndef submit_document_batch(self, graph_id, chunks, *, batch_size=350, ...):\n    if not graph_id:\n        raise ValueError(\n            \"graph_id is required: create the graph and persist its id before submitting a batch\"\n        )\n# caller\nif not project.graph_id:\n    project.graph_id = builder.create_graph(name=project.name)","handlingStrategy":"validation","validationCode":"if not graph_id:\n    graph_id = builder.create_graph(name=project.name)\n    project.graph_id = graph_id\nsubmission = builder.submit_document_batch(graph_id, chunks, batch_size=batch_size)","typeGuard":"def has_graph_id(project) -> bool:\n    return bool(getattr(project, 'graph_id', None))","tryCatchPattern":"try:\n    builder.submit_document_batch(graph_id, chunks, batch_size=batch_size)\nexcept ValueError as e:\n    if 'graph_id is required' in str(e):\n        raise HTTPException(status_code=409, detail='Project has no graph; create one first') from e\n    raise","preventionTips":["Provision the graph before any batch submission and persist its id on the project.","Guard delete/rebuild vs submit with the same per-graph lifecycle lock.","Assert project.graph_id is set at the API layer before calling the builder.","Re-create the graph if a failed delete flow cleared graph_id mid-build."],"tags":["backend","python","zep","validation","precondition"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}