{"record":{"id":"d1b28a618ff38284","repo":"HKUDS/DeepTutor","slug":"exc-d1b28a","errorCode":null,"errorMessage":"{exc}","messagePattern":"\\{exc\\}","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"deeptutor/api/routers/memory.py","lineNumber":349,"sourceCode":"        if req.llm_selection\n        else None\n    )\n    try:\n        run = await manager.start(\n            layer=lyr,\n            key=req.key,\n            mode=req.mode,\n            runner=runner,\n            params={\n                \"budget\": req.budget,\n                \"iterations\": req.iterations,\n                \"language\": req.language,\n                \"llm_selection\": selection,\n            },\n            language=req.language,\n        )\n    except RunBusyError as exc:\n        raise HTTPException(status_code=409, detail=str(exc))\n    return run.to_dict()\n\n\n@router.get(\"/runs/{run_id}\")\nasync def get_run(run_id: str):\n    from deeptutor.services.memory.consolidator.runs import get_run_manager\n\n    run = get_run_manager().get(run_id)\n    if run is None:\n        raise HTTPException(status_code=404, detail=\"unknown run_id\")\n    return run.to_dict()\n\n\n@router.post(\"/runs/{run_id}/cancel\")\nasync def cancel_run(run_id: str):\n    from deeptutor.services.memory.consolidator.runs import get_run_manager\n\n    ok = await get_run_manager().cancel(run_id)","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/api/routers/memory.py#L331-L367","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst res = await fetch('/memory/runs', {method:'POST'});\nif (res.status === 409) throw new Error('start failed');\n// after\nconst res = await fetch('/memory/runs', {method:'POST'});\nif (res.status === 409) {\n  await waitForActiveRunToFinish(); // poll GET /runs/{id}\n  return fetch('/memory/runs', {method:'POST'});\n}","handlingStrategy":"retry","validationCode":"const active = await (await fetch('/memory/runs?active=true')).json();\nif (active.length > 0) await waitForRun(active[0].run_id);","typeGuard":null,"tryCatchPattern":"try { await startRun(payload); } catch (e) { if (e.status === 409) { await pollUntilIdle(); await startRun(payload); } else throw e; }","preventionTips":["Disable the start button while any run is active","Poll run status before issuing a new start","Serialize consolidation triggers through one queue"],"tags":["memory","consolidation","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"}