HKUDS/DeepTutor · warning · HTTPException
{exc}
Error message
{exc} What it means
Raised as HTTP 409 when starting a memory consolidation run fails because the run manager rejects the request with RunBusyError — typically another consolidation run is already active for the same memory state. The router maps the domain exception to a Conflict status with the exception's message as the detail.
Source
Thrown at deeptutor/api/routers/memory.py:349
if req.llm_selection
else None
)
try:
run = await manager.start(
layer=lyr,
key=req.key,
mode=req.mode,
runner=runner,
params={
"budget": req.budget,
"iterations": req.iterations,
"language": req.language,
"llm_selection": selection,
},
language=req.language,
)
except RunBusyError as exc:
raise HTTPException(status_code=409, detail=str(exc))
return run.to_dict()
@router.get("/runs/{run_id}")
async def get_run(run_id: str):
from deeptutor.services.memory.consolidator.runs import get_run_manager
run = get_run_manager().get(run_id)
if run is None:
raise HTTPException(status_code=404, detail="unknown run_id")
return run.to_dict()
@router.post("/runs/{run_id}/cancel")
async def cancel_run(run_id: str):
from deeptutor.services.memory.consolidator.runs import get_run_manager
ok = await get_run_manager().cancel(run_id)View on GitHub (pinned to 3e82f13042)
Solutions
- Poll GET /runs/{run_id} (or the active-run listing) and wait for the current run to finish before starting a new one
- Add client-side debounce/lock on the trigger action so concurrent POSTs cannot overlap
- Cancel the active run via POST /runs/{run_id}/cancel, then retry the start
- If overlap is expected in your workflow, serialize runs through a queue instead of firing concurrent requests
Example fix
// before
const res = await fetch('/memory/runs', {method:'POST'});
if (res.status === 409) throw new Error('start failed');
// after
const res = await fetch('/memory/runs', {method:'POST'});
if (res.status === 409) {
await waitForActiveRunToFinish(); // poll GET /runs/{id}
return fetch('/memory/runs', {method:'POST'});
} Defensive patterns
Strategy: retry
Validate before calling
const active = await (await fetch('/memory/runs?active=true')).json();
if (active.length > 0) await waitForRun(active[0].run_id); Try / catch
try { await startRun(payload); } catch (e) { if (e.status === 409) { await pollUntilIdle(); await startRun(payload); } else throw e; } Prevention
- Disable the start button while any run is active
- Poll run status before issuing a new start
- Serialize consolidation triggers through one queue
When it happens
Trigger: POST to the memory runs endpoint (start_run) while another consolidation run is still in-flight; the underlying get_run_manager().start (or equivalent) raises RunBusyError and the handler converts it to 409.
Common situations: Clients double-clicking a 'Consolidate now' button, retry logic that re-POSTs before polling run status, or background schedules overlapping with a user-triggered run.
Related errors
- {RunBusyError message}
- not active
- unknown run_id
- nothing to undo
- preferences.md is not auto-consolidated
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/d1b28a618ff38284.
Report an issue: GitHub.