{"record":{"id":"2b7e7243adb4ce7a","repo":"666ghj/MiroFish","slug":"zep-updater-for-simulation-id-is-still-active","errorCode":null,"errorMessage":"Zep updater for {simulation_id} is still active","messagePattern":"Zep updater for (.+?) is still active","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"backend/app/services/zep_graph_memory_updater.py","lineNumber":717,"sourceCode":"    def get_simulation_ids(cls) -> List[str]:\n        \"\"\"Return every simulation with a retained updater.\"\"\"\n\n        with cls._lock:\n            return sorted(cls._updaters)\n\n    @classmethod\n    def discard_inactive_updater(cls, simulation_id: str) -> bool:\n        \"\"\"Discard a failed, fully stopped updater during graph destruction.\"\"\"\n\n        with cls._lock:\n            updater = cls._updaters.get(simulation_id)\n            if updater is None:\n                return False\n            worker_alive = bool(\n                updater._worker_thread and updater._worker_thread.is_alive()\n            )\n            if updater._running or worker_alive:\n                raise RuntimeError(\n                    f\"Zep updater for {simulation_id} is still active\"\n                )\n            cls._updaters.pop(simulation_id, None)\n        logger.warning(\n            \"Discarded incomplete Zep updater during explicit graph deletion: \"\n            \"simulation_id=%s, graph_id=%s\",\n            simulation_id,\n            updater.graph_id,\n        )\n        return True\n    \n    @classmethod\n    def stop_updater(cls, simulation_id: str):\n        \"\"\"停止并移除模拟的更新器\"\"\"\n        with cls._lock:\n            updater = cls._updaters.get(simulation_id)\n        if updater is None:\n            return","sourceCodeStart":699,"sourceCodeEnd":735,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/backend/app/services/zep_graph_memory_updater.py#L699-L735","documentation":"Raised by ZepGraphMemoryUpdater.discard_inactive_updater when graph destruction tries to remove an updater from the class-level registry but the updater still has _running set or its worker thread is alive. The guard exists so explicit graph deletion never discards an updater that is still writing to the graph being destroyed.","triggerScenarios":"Calling discard_inactive_updater(simulation_id) after stop() failed or before the worker thread fully exited (stop initiated but thread.join not completed, or stop raised and left _running true). The class lock is held while raising, so the updater stays registered.","commonSituations":"Error-path cleanup: a previous stop() raised (e.g. Zep API error during final flush) leaving the updater half-stopped, then the user deletes the graph; or a race where deletion is requested moments after stop while the worker is mid-batch.","solutions":["Call stop() (with a drain deadline) and wait for the worker thread to join before discard_inactive_updater","If stop() previously failed, inspect updater state (get_stats / logs) and retry stop with a longer budget","Only after the updater is fully quiescent call discard_inactive_updater again","Never force-pop the registry entry while the thread is alive — the thread would keep writing to a deleted graph"],"exampleFix":"# before\nupdater.stop()\nZepGraphMemoryUpdater.discard_inactive_updater(sim_id)  # thread may still be alive\n\n# after\nupdater.stop()\nupdater._worker_thread.join(timeout=30)\nif updater._worker_thread.is_alive():\n    raise RuntimeError(\"worker thread did not exit; refusing to delete graph\")\nZepGraphMemoryUpdater.discard_inactive_updater(sim_id)","handlingStrategy":"validation","validationCode":"updater = ZepGraphMemoryUpdater._updaters.get(sim_id)\nif updater and (updater._running or (updater._worker_thread and updater._worker_thread.is_alive())):\n    raise RuntimeError(f\"cannot delete graph: updater for {sim_id} still active\")\nZepGraphMemoryUpdater.discard_inactive_updater(sim_id)","typeGuard":null,"tryCatchPattern":"try:\n    ZepGraphMemoryUpdater.discard_inactive_updater(sim_id)\nexcept RuntimeError:\n    # stop properly first, then retry discard\n    updater.stop(); updater._worker_thread.join(timeout=30)\n    ZepGraphMemoryUpdater.discard_inactive_updater(sim_id)","preventionTips":["Always stop() and join the worker thread before graph deletion","Treat a failed stop() as blocking: do not proceed to deletion until resolved","Never bypass the registry guard; a live thread writing to a deleted graph corrupts state"],"tags":["zep","lifecycle","concurrency","cleanup"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}