{"record":{"id":"5527212a8a0bd808","repo":"666ghj/MiroFish","slug":"zep-episode-processing-timed-out-with-len-pending","errorCode":null,"errorMessage":"Zep episode processing timed out with {len(pending_episodes)} episode(s) still pending","messagePattern":"Zep episode processing timed out with (.+?) episode\\(s\\) still pending","errorType":"exception","errorClass":"TimeoutError","httpStatus":null,"severity":"error","filePath":"backend/app/services/graph_builder.py","lineNumber":747,"sourceCode":"                progress_callback(t('progress.noEpisodesWait'), 1.0)\n            return\n        \n        start_time = time.time()\n        pending_episodes = set(episode_uuids)\n        completed_count = 0\n        total_episodes = len(episode_uuids)\n        \n        if progress_callback:\n            progress_callback(t('progress.waitingEpisodes', count=total_episodes), 0)\n        \n        while pending_episodes:\n            if time.time() - start_time > timeout:\n                if progress_callback:\n                    progress_callback(\n                        t('progress.episodesTimeout', completed=completed_count, total=total_episodes),\n                        completed_count / total_episodes\n                    )\n                raise TimeoutError(\n                    f\"Zep episode processing timed out with \"\n                    f\"{len(pending_episodes)} episode(s) still pending\"\n                )\n            \n            # 检查每个 episode 的处理状态\n            for ep_uuid in list(pending_episodes):\n                episode = call_zep_read_with_retry(\n                    lambda: self.client.graph.episode.get(uuid_=ep_uuid),\n                    operation_name=f\"poll episode {ep_uuid}\",\n                )\n                is_processed = getattr(episode, 'processed', False)\n\n                if is_processed:\n                    pending_episodes.remove(ep_uuid)\n                    completed_count += 1\n            \n            elapsed = int(time.time() - start_time)\n            if progress_callback:","sourceCodeStart":729,"sourceCodeEnd":765,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/services/graph_builder.py#L729-L765","documentation":"Raised by GraphBuilder._wait_for_episodes when polling client.graph.episode.get(uuid_=...) shows episodes still not processed (episode.processed is falsy) after the timeout window. After a batch upload, Zep asynchronously processes each episode into graph entities/edges; this loop blocks until all episode_uuids report processed=True or the deadline (default derived from ZEP_INGESTION_WAIT_TIMEOUT_SECONDS = 600s) expires, then raises TimeoutError.","triggerScenarios":"Many episodes queued for graph processing at once (large batch succeeded, graph extraction backlog exceeds the wait window); Zep cloud under load; a few episodes stuck unprocessed indefinitely on Zep's side; timeout passed too small by the caller.","commonSituations":"Ingesting hundreds of chunks in one go and immediately waiting for graph readiness; peak-hour Zep latency; following a batch that 'succeeded' quickly but whose graph extraction is slow.","solutions":["Increase the episode wait timeout proportional to episode count (graph extraction is far slower than batch acceptance).","Re-poll the pending episodes after the timeout — most complete shortly after; only treat permanently unprocessed episodes as failures.","Ingest in smaller batches so the per-batch episode queue stays shallow.","If specific episode UUIDs stay unprocessed for hours, escalate to Zep support with the UUIDs."],"exampleFix":"# before\nbuilder._wait_for_episodes(episode_uuids, timeout=600)\n# after\ntimeout = max(600, 30 * len(episode_uuids))\nbuilder._wait_for_episodes(episode_uuids, timeout=timeout)","handlingStrategy":"retry","validationCode":"timeout = max(600, 30 * len(episode_uuids))  # graph extraction is slower than batch acceptance","typeGuard":null,"tryCatchPattern":"try:\n    builder._wait_for_episodes(episode_uuids, timeout=timeout)\nexcept TimeoutError:\n    still_pending = [u for u in episode_uuids if poll_later(u)]\n    if still_pending:\n        escalate(still_pending)\n    # else: all completed shortly after the deadline; continue","preventionTips":["Scale the episode wait with the number of episodes.","Ingest in smaller batches so graph extraction keeps up.","Treat the first timeout as a signal to re-poll, not to fail the run."],"tags":["zep","timeout","episodes","graph-processing","polling"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}