HKUDS/DeepTutor · error · HTTPException

Mastery path changed activity while the operation was…

Error message

Mastery path changed activity while the operation was starting; active session: {exc.lease.session_id}

What it means

Serialization guard in _exclusive_path_mutation: while the API was cancelling the active tutor turn and acquiring the path lease, another writer (a racing tutor session or API mutation holding session named in exc.lease.session_id) grabbed the lease, so the operation is refused to avoid concurrent mutation of the same mastery path.

Solutions

  1. Retry the operation once the conflicting session releases or renews the lease
  2. Cancel or finish the active learning turn holding the lease, then repeat the API call
  3. Check exc.lease.session_id to identify which writer won the race before retrying
Defensive patterns

Strategy: retry

When it happens

Trigger: Thrown at deeptutor/api/routers/mastery_path.py:127 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at deeptutor/api/routers/mastery_path.py:127

@asynccontextmanager
async def _exclusive_path_mutation(book_id: str):
    """Cancel the tutor, then exclude a newly racing tutor/API write."""
    from deeptutor.learning.storage import PathLeaseConflictError

    await _cancel_active_learning_turn(book_id)
    store = LearningStore()
    operation_id = f"api-{uuid.uuid4().hex}"
    try:
        await asyncio.to_thread(
            store.acquire_path_lease,
            book_id,
            "__path_api__",
            operation_id,
            bind_session=False,
        )
    except PathLeaseConflictError as exc:
        raise HTTPException(
            status_code=409,
            detail=(
                "Mastery path changed activity while the operation was starting; "
                f"active session: {exc.lease.session_id}"
            ),
        ) from exc
    try:
        yield
    finally:
        await asyncio.to_thread(
            store.release_path_lease,
            book_id,
            turn_id=operation_id,
        )


# ── Request models ───────────────────────────────────────────────────────────

View on GitHub (pinned to 3e82f13042)