{"record":{"id":"8c958b43912edd2d","repo":"HKUDS/DeepTutor","slug":"cancel-the-active-run-before-undoing-memory-edits","errorCode":null,"errorMessage":"cancel the active run before undoing memory edits","messagePattern":"cancel the active run before undoing memory edits","errorType":"error_code","errorClass":"RunBusyError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/memory/consolidator/runs.py","lineNumber":210,"sourceCode":"        run._task = asyncio.create_task(self._drive(run, runner))\n        return run\n\n    async def cancel(self, run_id: str) -> bool:\n        run = self._runs.get(run_id)\n        if run is None or not run.active:\n            return False\n        run._cancel_flag.set()\n        if run._task is not None and not run._task.done():\n            run._task.cancel()\n        return True\n\n    async def undo_last(self, run_id: str) -> RunEvent | None:\n        \"\"\"Restore the document snapshot before the latest run write.\"\"\"\n        run = self._runs.get(run_id)\n        if run is None:\n            raise KeyError(run_id)\n        if run.active:\n            raise RunBusyError(\"cancel the active run before undoing memory edits\")\n        if not run.undo_stack:\n            return None\n\n        checkpoint = run.undo_stack.pop()\n        path = Path(checkpoint.path)\n        if checkpoint.existed:\n            await asyncio.to_thread(_atomic_write, path, checkpoint.previous_content)\n        else:\n            await asyncio.to_thread(_remove_if_exists, path)\n\n        return await self._emit(\n            run,\n            {\n                \"stage\": \"undo_applied\",\n                \"run_id\": run.id,\n                \"undo_id\": checkpoint.id,\n                \"undo_depth\": len(run.undo_stack),\n                \"layer\": checkpoint.layer,","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/memory/consolidator/runs.py#L192-L228","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nrun = await manager.start(...)\n# ... run emits writes but hasn't finished\nawait manager.undo_last(run.id)\n\n// after\nrun = await manager.start(...)\nawait manager.wait_until_done(run.id)  # or await the runner\nawait manager.undo_last(run.id)","handlingStrategy":"validation","validationCode":"run = manager.get(run_id)\nif run is None or run.active:\n    raise RuntimeError(\"run must be finished before undo\")\nevent = await manager.undo_last(run_id)","typeGuard":"def can_undo(run) -> bool:\n    return (not run.active) and bool(run.undo_stack)","tryCatchPattern":"try:\n    await manager.undo_last(run_id)\nexcept RunBusyError:\n    await manager.cancel(run_id)\n    await manager.undo_last(run_id)","preventionTips":["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"],"tags":["undo","concurrency","memory","run-lifecycle"],"backgroundTag":"operation-on-active-resource","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}