HKUDS/DeepTutor · warning · HTTPException

not active

Error message

not active

What it means

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.

Source

Thrown at deeptutor/api/routers/memory.py:369


@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)
    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:

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Treat 409 as 'already terminal': re-fetch GET /runs/{run_id} and update UI to the run's final state
  2. Guard cancel buttons with the run status and disable once state is not active
  3. Idempotency-check client-side before re-sending cancel requests

Example fix

// before
await fetch(`/memory/runs/${id}/cancel`, {method:'POST'});
// after
const run = await (await fetch(`/memory/runs/${id}`)).json();
if (run.state === 'running' || run.state === 'pending') {
  await fetch(`/memory/runs/${id}/cancel`, {method:'POST'});
}
Defensive patterns

Strategy: validation

Validate before calling

const run = await getRun(id);
if (!['running','pending'].includes(run.state)) skipCancel();

Type guard

function isCancellable(state: string): boolean { return state === 'running' || state === 'pending'; }

Try / catch

try { await cancelRun(id); } catch (e) { if (e.status === 409) refreshRunState(id); else throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: UI showing a stale 'running' badge while the run finished, retry storms from an aggressive cancel loop, or double-clicking a cancel button.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/d65d4ba70b523fd2. Report an issue: GitHub.