{"record":{"id":"d65d4ba70b523fd2","repo":"HKUDS/DeepTutor","slug":"not-active","errorCode":null,"errorMessage":"not active","messagePattern":"not active","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"deeptutor/api/routers/memory.py","lineNumber":369,"sourceCode":"\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)\n    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:","sourceCodeStart":351,"sourceCodeEnd":387,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/api/routers/memory.py#L351-L387","documentation":"HTTP 409 from POST /memory/runs/{run_id}/cancel when the run manager's cancel() returns falsy — the run exists but is not in a cancellable (active) state. Runs that already completed, failed, or were cancelled cannot be cancelled again.","triggerScenarios":"Cancelling a run that already finished or was previously cancelled; a race where the run completes between the client's status check and the cancel request.","commonSituations":"UI showing a stale 'running' badge while the run finished, retry storms from an aggressive cancel loop, or double-clicking a cancel button.","solutions":["Treat 409 as 'already terminal': re-fetch GET /runs/{run_id} and update UI to the run's final state","Guard cancel buttons with the run status and disable once state is not active","Idempotency-check client-side before re-sending cancel requests"],"exampleFix":"// before\nawait fetch(`/memory/runs/${id}/cancel`, {method:'POST'});\n// after\nconst run = await (await fetch(`/memory/runs/${id}`)).json();\nif (run.state === 'running' || run.state === 'pending') {\n  await fetch(`/memory/runs/${id}/cancel`, {method:'POST'});\n}","handlingStrategy":"validation","validationCode":"const run = await getRun(id);\nif (!['running','pending'].includes(run.state)) skipCancel();","typeGuard":"function isCancellable(state: string): boolean { return state === 'running' || state === 'pending'; }","tryCatchPattern":"try { await cancelRun(id); } catch (e) { if (e.status === 409) refreshRunState(id); else throw e; }","preventionTips":["Gate cancel on the polled run state","Disable cancel once state is terminal","Debounce cancel button clicks"],"tags":["memory","cancellation","http-409","run-manager","idempotency"],"backgroundTag":"invalid-state-transition","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}