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 taskView on GitHub (pinned to e90f1c7592)
Solutions
- Wait for or cancel the existing run before starting a new one for the session
- Acquire the distributed session lock around spawn so concurrent workers serialize per-session runs
- On the client side, disable/queue new submissions while a run is active (surface 409)
- 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
- Hold the distributed session lock around per-session spawns
- Expose run status so callers can check before spawning
- Implement client-side submission locking per session
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
- One or more tool calls raised an exception
- Session {session_id!r} not found for agent {agent_id!r}.
- Input validation failed for tool '{tool_call.name}': {e.mess
- Invalid permission decision behavior: {decision.behavior}
- Model call failed after retries, but no exception was raised
AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28).
Data as JSON: /api/errors/0d6673ec40392cf0.
Report an issue: GitHub.