{"record":{"id":"057f58956c09994b","repo":"HKUDS/DeepTutor","slug":"runbusyerror-message","errorCode":null,"errorMessage":"{RunBusyError message}","messagePattern":"\\{RunBusyError message\\}","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"deeptutor/api/routers/memory.py","lineNumber":386,"sourceCode":"    if not ok:\n        raise HTTPException(status_code=409, detail=\"not active\")\n    return {\"run_id\": run_id, \"cancelled\": True}\n\n\n@router.post(\"/runs/{run_id}/undo\")\nasync def undo_run_edit(run_id: str):\n    from deeptutor.services.memory.consolidator.runs import (\n        RunBusyError,\n        get_run_manager,\n    )\n\n    manager = get_run_manager()\n    try:\n        event = await manager.undo_last(run_id)\n    except KeyError:\n        raise HTTPException(status_code=404, detail=\"unknown run_id\")\n    except RunBusyError as exc:\n        raise HTTPException(status_code=409, detail=str(exc))\n    if event is None:\n        raise HTTPException(status_code=409, detail=\"nothing to undo\")\n    run = manager.get(run_id)\n    return {\n        \"run_id\": run_id,\n        \"undone\": True,\n        \"undo_count\": len(run.undo_stack) if run else 0,\n        \"event\": {\"seq\": event.seq, \"ts\": event.ts, **event.payload},\n    }\n\n\n@router.get(\"/runs\")\nasync def list_runs(layer: str | None = None, key: str | None = None):\n    from deeptutor.services.memory.consolidator.runs import get_run_manager\n\n    lyr = _validate_layer(layer) if layer is not None else None\n    if lyr and key is not None:\n        _validate_doc_key(lyr, key)","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/api/routers/memory.py#L368-L404","documentation":"HTTP 409 from POST /memory/runs/{run_id}/undo when undo_last raises RunBusyError — the run (or the memory subsystem it targets) is currently active/busy and cannot accept an undo while work is in flight.","triggerScenarios":"Calling undo while the consolidation run is still executing, or while another exclusive operation (another undo, a cancel, a new run) holds the run manager's lock.","commonSituations":"Users clicking 'undo' as soon as results stream in before the run reaches a terminal state, or concurrent undo + cancel requests racing each other.","solutions":["Wait for the run to reach a completed state (poll GET /runs/{run_id}) before calling undo","Disable the undo action in the UI while run state is active","Serialize exclusive run operations (undo/cancel/start) behind a single client-side queue"],"exampleFix":"// before\nawait fetch(`/memory/runs/${id}/undo`, {method:'POST'});\n// after\nconst run = await (await fetch(`/memory/runs/${id}`)).json();\nif (run.state === 'completed') {\n  await fetch(`/memory/runs/${id}/undo`, {method:'POST'});\n}","handlingStrategy":"retry","validationCode":"const run = await getRun(id);\nif (run.state !== 'completed') await waitTerminal(id);","typeGuard":"function isTerminal(state: string): boolean { return ['completed','failed','cancelled'].includes(state); }","tryCatchPattern":"try { await undoRun(id); } catch (e) { if (e.status === 409) { await waitTerminal(id); await undoRun(id); } else throw e; }","preventionTips":["Only expose undo after the run reaches a terminal state","Serialize undo/cancel/start requests client-side"],"tags":["memory","undo","http-409","conflict","run-manager"],"backgroundTag":"http-409-conflict","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}