agentscope-ai/agentscope · error · RuntimeError

Session {session_id!r} already has an active chat run in thi

Error message

Session {session_id!r} already has an active chat run in this process.

What it means

The per-process ChatRunRegistry refuses to spawn a second asyncio task for a session that already has a live (not done) chat run. It enforces one active run per session per process; callers are expected to coordinate via the distributed session lock before spawning.

Source

Thrown at src/agentscope/app/_manager/_chat_run_registry.py:74

                Optional task name passed through to
                :func:`asyncio.create_task`. Defaults to
                ``f"chat-run:{session_id}"``.

        Returns:
            `asyncio.Task`:
                The created task. Callers normally do not need to keep
                the reference — the registry holds it for the task's
                lifetime.

        Raises:
            `RuntimeError`:
                When a non-finished task is already registered for
                ``session_id``. Callers are expected to coordinate via
                the distributed session lock before spawning.
        """
        existing = self._tasks.get(session_id)
        if existing is not None and not existing.done():
            raise RuntimeError(
                f"Session {session_id!r} already has an active chat run "
                "in this process.",
            )

        task = asyncio.create_task(
            coro,
            name=name or f"chat-run:{session_id}",
        )
        self._tasks[session_id] = task

        def _cleanup(t: asyncio.Task) -> None:
            # Only remove the entry if it still points at this task —
            # a fresh spawn for the same sid may have replaced it.
            if self._tasks.get(session_id) is t:
                self._tasks.pop(session_id, None)

        task.add_done_callback(_cleanup)
        return task

View on GitHub (pinned to e90f1c7592)

Solutions

  1. Wait for or cancel the existing run before starting a new one for the session
  2. Acquire the distributed session lock around spawn so concurrent workers serialize per-session runs
  3. On the client side, disable/queue new submissions while a run is active (surface 409)
  4. If the old run is stuck, cancel it via the interrupt/cancel API then retry

Example fix

# before
resp = requests.post(f"/chat", json={...})  # second click -> 409

# after
async with session_lock(request.session_id):
    existing = registry.get(request.session_id)
    if existing and not existing.done():
        raise BusyError("session busy")
    await registry.spawn(request.session_id, coro)
Defensive patterns

Strategy: try-catch

Validate before calling

existing = registry._tasks.get(session_id)
busy = existing is not None and not existing.done()

Try / catch

try:
    await registry.spawn(session_id, coro)
except RuntimeError as e:
    if "already has an active chat run" in str(e):
        await interrupt(session_id)  # or wait for completion

Prevention

When it happens

Trigger: Calling chat()/dispatch twice for the same session_id before the first run finishes, or when a previous task was never awaited/cancelled and is still pending.

Common situations: Double-clicking a send button where the frontend fires two chat requests; a webhook and a user action racing for the same session; retrying a request while the previous run is still executing; missing distributed session lock when running the app behind multiple workers.

Related errors


AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28). Data as JSON: /api/errors/0d6673ec40392cf0. Report an issue: GitHub.