666ghj/MiroFish · error · RuntimeError
Zep updater for is still active
Error message
Zep updater for {simulation_id} is still active What it means
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.
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
Example fix
# before
updater.stop()
ZepGraphMemoryUpdater.discard_inactive_updater(sim_id) # thread may still be alive
# after
updater.stop()
updater._worker_thread.join(timeout=30)
if updater._worker_thread.is_alive():
raise RuntimeError("worker thread did not exit; refusing to delete graph")
ZepGraphMemoryUpdater.discard_inactive_updater(sim_id) Defensive patterns
Strategy: validation
Validate before calling
updater = ZepGraphMemoryUpdater._updaters.get(sim_id)
if updater and (updater._running or (updater._worker_thread and updater._worker_thread.is_alive())):
raise RuntimeError(f"cannot delete graph: updater for {sim_id} still active")
ZepGraphMemoryUpdater.discard_inactive_updater(sim_id) Try / catch
try:
ZepGraphMemoryUpdater.discard_inactive_updater(sim_id)
except RuntimeError:
# stop properly first, then retry discard
updater.stop(); updater._worker_thread.join(timeout=30)
ZepGraphMemoryUpdater.discard_inactive_updater(sim_id) Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Graph is in use by active consumer(s)
- 部分图谱更新器未完整停止
- production edge pagination did not match raw cursor…
- production node pagination did not match raw cursor…
- A Zep batch cannot contain more than 50,000 items
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/2b7e7243adb4ce7a.
Report an issue: GitHub.
Appendix: source
Thrown at backend/app/services/zep_graph_memory_updater.py:717
def get_simulation_ids(cls) -> List[str]:
"""Return every simulation with a retained updater."""
with cls._lock:
return sorted(cls._updaters)
@classmethod
def discard_inactive_updater(cls, simulation_id: str) -> bool:
"""Discard a failed, fully stopped updater during graph destruction."""
with cls._lock:
updater = cls._updaters.get(simulation_id)
if updater is None:
return False
worker_alive = bool(
updater._worker_thread and updater._worker_thread.is_alive()
)
if updater._running or worker_alive:
raise RuntimeError(
f"Zep updater for {simulation_id} is still active"
)
cls._updaters.pop(simulation_id, None)
logger.warning(
"Discarded incomplete Zep updater during explicit graph deletion: "
"simulation_id=%s, graph_id=%s",
simulation_id,
updater.graph_id,
)
return True
@classmethod
def stop_updater(cls, simulation_id: str):
"""停止并移除模拟的更新器"""
with cls._lock:
updater = cls._updaters.get(simulation_id)
if updater is None:
returnView on GitHub (pinned to b5b53acc57)