aio-libs/aiohttp · error · RuntimeError

Call runner.setup() before making a site

Error message

Call runner.setup() before making a site

What it means

Raised by BaseSite.__init__ (TCPSite, UnixSite, NamedPipeSite, etc.) when the supplied runner's `server` attribute is None, meaning runner.setup() was never awaited. Sites bind the runner's already-built server to a socket, so the server must exist first.

Solutions

  1. Always `await runner.setup()` BEFORE constructing any Site: `await runner.setup(); site = TCPSite(runner, host, port); await site.start()`.
  2. Use the `AppRunner` or `ServerRunner` lifecycle helpers in the documented order.
  3. In tests, run setup() inside the async test body, not in __init__.
  4. Use `web.run_app(app)` for the common case — it handles ordering internally.

Example fix

// before
runner = AppRunner(app)
site = TCPSite(runner, '0.0.0.0', 8080)  # RuntimeError: server is None
await runner.setup()
await site.start()

// after
runner = AppRunner(app)
await runner.setup()
site = TCPSite(runner, '0.0.0.0', 8080)
await site.start()
Defensive patterns

Strategy: validation

Validate before calling

async def make_site(runner, *args, **kw):
    if runner.server is None:
        await runner.setup()
    return TCPSite(runner, *args, **kw)

Type guard

def runner_is_ready(runner) -> bool:
    return runner.server is not None

Prevention

When it happens

Trigger: Constructing `TCPSite(runner)` (or any BaseSite subclass) before `await runner.setup()`. Common in code that builds the site eagerly at import time or in a synchronous helper, then awaits setup() later.

Common situations: Application factory ordering bugs where Site construction is moved before setup(); refactoring that splits setup() and start() across functions and swaps their order; testing harness that builds sites in setUp before the loop runs setup().

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/aac0922c4ae7ebc4. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/web_runner.py:103

    code = 1


def _raise_graceful_exit() -> None:
    raise GracefulExit()


class BaseSite(ABC):
    __slots__ = ("_runner", "_ssl_context", "_backlog", "_server")

    def __init__(
        self,
        runner: "BaseRunner[Any]",
        *,
        ssl_context: SSLContext | None = None,
        backlog: int = 128,
    ) -> None:
        if runner.server is None:
            raise RuntimeError("Call runner.setup() before making a site")
        self._runner = runner
        self._ssl_context = ssl_context
        self._backlog = backlog
        self._server: asyncio.Server | None = None

    @property
    @abstractmethod
    def name(self) -> str:
        """Return the name of the site (e.g. a URL)."""

    @abstractmethod
    async def start(self) -> None:
        self._runner._reg_site(self)

    async def stop(self) -> None:
        self._runner._check_site(self)
        if self._server is not None:  # Maybe not started yet
            self._server.close()

View on GitHub (pinned to d041d4d0fd)