langchain-ai/deepagents · error · RuntimeError

UnixSocketEventSource.serve_forever called before start()

Error message

UnixSocketEventSource.serve_forever called before start()

What it means

`serve_forever()` awaits the internal asyncio server created by `start()`. If `start()` was never called — or failed before creating the server — there is nothing to await, so the method raises this RuntimeError, documented as a strict start-before-serve contract.

Source

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

        # umask (different libc, mocked socket layer), force-tighten the mode.
        with contextlib.suppress(OSError):
            self.path.chmod(0o600)
        logger.debug("External event listener bound at %s", self.path)

    async def serve_forever(self) -> None:
        """Park until the underlying server is cancelled or fails.

        `asyncio.start_unix_server` already begins accepting connections,
        so this delegates to the server's own `serve_forever`. A fatal
        error inside the accept loop propagates here, letting the
        lifecycle owner notice and react.

        Raises:
            RuntimeError: If invoked before `start()`.
        """
        if self._server is None:
            msg = "UnixSocketEventSource.serve_forever called before start()"
            raise RuntimeError(msg)
        await self._server.serve_forever()

    async def stop(self) -> None:
        """Close the listener and remove the socket path.

        Idempotent: safe to call after a failed or never-started `start()`.
        """
        server = self._server
        self._server = None
        if server is not None:
            server.close()
            with contextlib.suppress(Exception):
                await server.wait_closed()
        try:
            _unlink_existing_socket(self.path)
        except FileNotFoundError:
            pass
        except FileExistsError as exc:

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Call `await source.start()` before `serve_forever()` — start binds the socket and creates the server.
  2. Ensure a failed `start()` is not silently swallowed; abort startup instead of proceeding to serve.
  3. Restructure startup so `serve_forever` runs only in a task spawned after a confirmed successful `start()`.

Example fix

// before
src = UnixSocketEventSource(path, sink)
await src.serve_forever()  # RuntimeError
// after
src = UnixSocketEventSource(path, sink)
await src.start()
await src.serve_forever()
Defensive patterns

Strategy: try-catch

Validate before calling

if getattr(source, "_server", None) is None:
    await source.start()
await source.serve_forever()

Type guard

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

Try / catch

try:
    await source.serve_forever()
except RuntimeError as exc:
    if "called before start" in str(exc):
        await source.start()
        await source.serve_forever()
    else:
        raise

Prevention

When it happens

Trigger: Calling `await source.serve_forever()` directly after constructing `UnixSocketEventSource` without first calling `await source.start()`; also when a prior `start()` raised (e.g. bind failure) leaving `_server` as None.

Common situations: Copy-pasted serve loops from other asyncio servers that create the server inside `serve_forever`; background tasks launched before initialization completes; a swallowed exception from `start()` letting execution continue to the serve call.

Related errors


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