HKUDS/DeepTutor · warning · HTTPException
{RunBusyError message}
Error message
{RunBusyError message} What it means
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.
Source
Thrown at deeptutor/api/routers/memory.py:386
if not ok:
raise HTTPException(status_code=409, detail="not active")
return {"run_id": run_id, "cancelled": True}
@router.post("/runs/{run_id}/undo")
async def undo_run_edit(run_id: str):
from deeptutor.services.memory.consolidator.runs import (
RunBusyError,
get_run_manager,
)
manager = get_run_manager()
try:
event = await manager.undo_last(run_id)
except KeyError:
raise HTTPException(status_code=404, detail="unknown run_id")
except RunBusyError as exc:
raise HTTPException(status_code=409, detail=str(exc))
if event is None:
raise HTTPException(status_code=409, detail="nothing to undo")
run = manager.get(run_id)
return {
"run_id": run_id,
"undone": True,
"undo_count": len(run.undo_stack) if run else 0,
"event": {"seq": event.seq, "ts": event.ts, **event.payload},
}
@router.get("/runs")
async def list_runs(layer: str | None = None, key: str | None = None):
from deeptutor.services.memory.consolidator.runs import get_run_manager
lyr = _validate_layer(layer) if layer is not None else None
if lyr and key is not None:
_validate_doc_key(lyr, key)View on GitHub (pinned to 3e82f13042)
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
Example fix
// before
await fetch(`/memory/runs/${id}/undo`, {method:'POST'});
// after
const run = await (await fetch(`/memory/runs/${id}`)).json();
if (run.state === 'completed') {
await fetch(`/memory/runs/${id}/undo`, {method:'POST'});
} Defensive patterns
Strategy: retry
Validate before calling
const run = await getRun(id); if (run.state !== 'completed') await waitTerminal(id);
Type guard
function isTerminal(state: string): boolean { return ['completed','failed','cancelled'].includes(state); } Try / catch
try { await undoRun(id); } catch (e) { if (e.status === 409) { await waitTerminal(id); await undoRun(id); } else throw e; } Prevention
- Only expose undo after the run reaches a terminal state
- Serialize undo/cancel/start requests client-side
When it happens
Trigger: 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.
Common situations: Users clicking 'undo' as soon as results stream in before the run reaches a terminal state, or concurrent undo + cancel requests racing each other.
Related errors
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/057f58956c09994b.
Report an issue: GitHub.