langchain-ai/deepagents · error · RuntimeError

UnixSocketEventSource is already started

Error message

UnixSocketEventSource is already started

What it means

`UnixSocketEventSource.start()` binds and serves on a Unix socket; calling it twice would attempt to rebind a socket the instance already owns. The method raises this RuntimeError when the internal server handle (`self._server`) is already set, enforcing a strict start-once lifecycle. `stop()` is the idempotent counterpart for cleanup.

Source

Thrown at libs/code/deepagents_code/event_bus.py:157

        self,
        sink: Callable[[ExternalEvent], Awaitable[None]],
    ) -> None:
        """Start listening for newline-delimited JSON events.

        Args:
            sink: Async callback invoked with each decoded event.

        Raises:
            RuntimeError: If `start()` has already been called on this
                instance without a subsequent `stop()`.
            FileExistsError: If the socket path is occupied by a non-socket
                filesystem entry.
            OSError: If the socket cannot be bound (e.g. permission denied,
                path too long).
        """  # noqa: DOC502  # FileExistsError/OSError propagate from helpers
        if self._server is not None:
            msg = "UnixSocketEventSource is already started"
            raise RuntimeError(msg)

        self._sink = sink
        self.path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
        with contextlib.suppress(FileNotFoundError):
            _unlink_existing_socket(self.path)

        previous_umask = os.umask(0o077)
        try:
            self._server = await asyncio.start_unix_server(
                self._handle_client,
                path=str(self.path),
                limit=_MAX_LINE_BYTES,
            )
        finally:
            os.umask(previous_umask)

        # Defense in depth: even if `start_unix_server` somehow ignored the
        # umask (different libc, mocked socket layer), force-tighten the mode.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Track a started flag yourself and skip `start()` when it is already set.
  2. Call `await source.stop()` before a deliberate restart, then `start()` again.
  3. Create a fresh `UnixSocketEventSource` instance for each start cycle instead of restarting the old one.
  4. Move startup to a single place in the app lifecycle so it runs exactly once.

Example fix

// before
async def reconnect(src):
    await src.start()  # RuntimeError if already started
// after
async def reconnect(src):
    await src.stop()
    await src.start()
Defensive patterns

Strategy: try-catch

Validate before calling

if getattr(source, "_server", None) is not None:
    return  # already started
await source.start()

Type guard

def is_started(source) -> bool:
    return getattr(source, "_server", None) is not None

Try / catch

try:
    await source.start()
except RuntimeError as exc:
    if "already started" in str(exc):
        logger.debug("Event source already running; skipping start")
    else:
        raise

Prevention

When it happens

Trigger: Calling `start()` a second time on the same instance — e.g. a reconnect routine that reuses the object, or an init path racing a background starter without checking state.

Common situations: Retry logic that restarts the same source instead of creating a new one; app reload hooks firing initialization twice; wiring that starts the source in both a screen handler and an app-level hook.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/a3fdcf161405bf18. Report an issue: GitHub.