HKUDS/DeepTutor · error · RunBusyError
cancel the active run before undoing memory edits
Error message
cancel the active run before undoing memory edits
What it means
RunManager.undo_last refuses to restore a document snapshot while the run that created it is still active. Undo pops a checkpoint from the run's undo_stack and rewrites the memory file, which would race with the run's own writes; therefore the run must be terminal (completed/cancelled) before undoing.
Source
Thrown at deeptutor/services/memory/consolidator/runs.py:210
run._task = asyncio.create_task(self._drive(run, runner))
return run
async def cancel(self, run_id: str) -> bool:
run = self._runs.get(run_id)
if run is None or not run.active:
return False
run._cancel_flag.set()
if run._task is not None and not run._task.done():
run._task.cancel()
return True
async def undo_last(self, run_id: str) -> RunEvent | None:
"""Restore the document snapshot before the latest run write."""
run = self._runs.get(run_id)
if run is None:
raise KeyError(run_id)
if run.active:
raise RunBusyError("cancel the active run before undoing memory edits")
if not run.undo_stack:
return None
checkpoint = run.undo_stack.pop()
path = Path(checkpoint.path)
if checkpoint.existed:
await asyncio.to_thread(_atomic_write, path, checkpoint.previous_content)
else:
await asyncio.to_thread(_remove_if_exists, path)
return await self._emit(
run,
{
"stage": "undo_applied",
"run_id": run.id,
"undo_id": checkpoint.id,
"undo_depth": len(run.undo_stack),
"layer": checkpoint.layer,View on GitHub (pinned to 3e82f13042)
Solutions
- Await the run's completion (or cancellation) before calling undo_last; undo is only valid on finished runs
- If the run appears stuck active, cancel it first via the manager's cancel API, then call undo_last
- Verify run.active is False / run status is terminal before invoking undo
Example fix
// before run = await manager.start(...) # ... run emits writes but hasn't finished await manager.undo_last(run.id) // after run = await manager.start(...) await manager.wait_until_done(run.id) # or await the runner await manager.undo_last(run.id)
Defensive patterns
Strategy: validation
Validate before calling
run = manager.get(run_id)
if run is None or run.active:
raise RuntimeError("run must be finished before undo")
event = await manager.undo_last(run_id) Type guard
def can_undo(run) -> bool:
return (not run.active) and bool(run.undo_stack) Try / catch
try:
await manager.undo_last(run_id)
except RunBusyError:
await manager.cancel(run_id)
await manager.undo_last(run_id) Prevention
- Await run completion before exposing undo in UIs/tests
- Check run.active and run.undo_stack before calling undo_last
- Cancel stuck runs rather than undoing under them
When it happens
Trigger: Calling undo_last(run_id) while run.active is True — e.g. undoing an edit immediately after a run's write events but before the run has fully finished, or undo_run_edit racing a long consolidation run.
Common situations: A UI 'undo' button wired to live run events; automated tests (test_undo_last_restores_previous_document) forgetting to await run completion before undoing; chaining undo immediately after start() without awaiting the runner.
Related errors
- {RunBusyError message}
- nothing to undo
- a run is already in progress for {layer}/{key}
- {exc}
- unknown run_id
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/8c958b43912edd2d.
Report an issue: GitHub.