langchain-ai/deepagents · error · RuntimeError
QuickJS context is closed
Error message
QuickJS context is closed
What it means
The QuickJS REPL raises this RuntimeError from `_require_ctx` when its internal `Context` handle is None, i.e. the REPL has been closed and its JS context released. Any lifecycle method (installing console/tools, registering the task bridge, snapshot/restore) that needs a live context checks this first and refuses to operate on a dead REPL. It protects against use-after-free of the underlying QuickJS VM.
Source
Thrown at libs/partners/quickjs/langchain_quickjs/_repl.py:450
self._task_calls: asyncio.Semaphore | None = None
# Context creation + console install must happen on the worker
# thread. Block caller here so the REPL is ready to use when
# __init__ returns.
worker.run_sync(self._ainit())
async def _ainit(self) -> None:
self._ctx = self._runtime.new_context(timeout=self._per_call_timeout)
if self._capture_console:
self._install_console()
if self._subagents_enabled:
self._task_calls = asyncio.Semaphore(_MAX_TASK_CALLS_PER_THREAD)
self._register_task_bridge()
def _require_ctx(self) -> Context:
"""Return the live QuickJS context or raise if this REPL is closed."""
if self._ctx is None:
msg = "QuickJS context is closed"
raise RuntimeError(msg)
return self._ctx
def _install_console(self) -> None:
ctx = self._require_ctx()
buf = self._console
@ctx.function(name="__console_log")
def _log(*args: Any) -> None:
buf.append("log", args)
@ctx.function(name="__console_warn")
def _warn(*args: Any) -> None:
buf.append("warn", args)
@ctx.function(name="__console_error")
def _error(*args: Any) -> None:
buf.append("error", args)
View on GitHub (pinned to a1af029e6e)
Solutions
- Create a fresh REPL instance instead of reusing the closed one
- Audit the code path so `install_tools`/snapshot calls only happen inside the REPL's lifetime (inside the `async with` block)
- Check `repl._ctx is not None` (or a public is_closed accessor) before calling lifecycle methods
Example fix
// before
repl.install_tools(tools) # after repl.close() -> RuntimeError
// after
if not repl.is_closed:
repl.install_tools(tools) Defensive patterns
Strategy: try-catch
Validate before calling
if repl is None or getattr(repl, '_ctx', None) is None:
repl = MyQuickJSRepl(...) # recreate before use Type guard
def repl_is_live(repl) -> bool:
return getattr(repl, '_ctx', None) is not None Try / catch
try:
repl.install_tools(tools)
except RuntimeError as e:
if 'context is closed' in str(e):
repl = recreate_repl(); repl.install_tools(tools)
else:
raise Prevention
- Keep all REPL usage inside its async context manager lifetime
- Never cache REPL objects beyond the eval that created them
- Add an is_closed guard helper before lifecycle calls
When it happens
Trigger: Calling any REPL method after `close()`/aclose was called, or after the context was torn down: `install_tools`, `_install_console`, `_register_task_bridge`, `_register_tool_bridge`, or `_acreate_snapshot`/`_arestore_snapshot`.
Common situations: Using the REPL after an async context-exit (`async with` block ended), reusing a cached REPL object that another code path closed, or a shutdown race where a background task tries to install tools during teardown.
Related errors
- Server process is not running
- A workspace is required to start the remote agent.
- Session start stopped by hook
- UnixSocketEventSource is already started
- UnixSocketEventSource.serve_forever called before start()
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/73ef931168118c60.
Report an issue: GitHub.