aio-libs/aiohttp · error · RuntimeError
wsgi app should be either Application or async function…
Error message
wsgi app should be either Application or async function returning Application, got {self.wsgi} What it means
Raised by GunicornWebWorker._run() when self.wsgi (the app object passed to gunicorn) is neither an aiohttp.web.Application instance nor a coroutine function returning an Application (or AppRunner). The gunicorn aiohttp worker only knows how to serve those two shapes; anything else is rejected at worker boot. The %s placeholder echoes the offending value.
Solutions
- Export an Application instance directly: `app = web.Application(); ...` and reference it in the gunicorn command.
- Or export an async factory that returns an Application: `async def app(): return web.Application()`.
- If returning a pre-configured runner, return a web.AppRunner from the coroutine (it is unwrapped at line 77-79).
- Verify with `inspect.iscoroutinefunction(obj)` and `isinstance(obj, web.Application)` before deploying.
Example fix
// before
# module:app
app = web.Application # the class, not an instance -> raises
// after
# module:app
app = web.Application()
# or a factory:
async def app():
application = web.Application()
application.router.add_get('/', handler)
return application Defensive patterns
Strategy: validation
Validate before calling
import inspect
from aiohttp import web
assert isinstance(app, web.Application) or inspect.iscoroutinefunction(app), \
f'wsgi must be Application or async factory, got {type(app)}' Type guard
def is_valid_wsgi(obj) -> bool:
import inspect
from aiohttp import web
return isinstance(obj, web.Application) or inspect.iscoroutinefunction(obj) Try / catch
# raised at worker boot; fix the exported symbol rather than catching at runtime.
# In a startup self-test:
try:
import module
assert is_valid_wsgi(getattr(module, 'app'))
except (ImportError, AssertionError) as e:
fail_build(f'gunicorn app target invalid: {e}') Prevention
- Export an Application instance or an `async def` factory from the module pointed at by gunicorn.
- Add a smoke import in CI: load the module and assert the wsgi target type.
- If using a factory, ensure it returns web.Application or web.AppRunner.
When it happens
Trigger: Pointing gunicorn at a plain function (not async), a class, a string module path, an async function that returns something other than Application/AppRunner, or a sync factory. Set via `gunicorn ... module:app` where `app` resolves to the wrong type.
Common situations: Exporting a factory function but forgetting `async def`; exporting the module or a router instead of the Application; returning a web.Server or TCPSite instead of Application/AppRunner from the factory.
Related errors
- Gunicorn's style options in form of `%(name)s` are not…
- SSL is not supported.
- data argument must be byte-ish (%r)
- Received message : is not WSMsgType.BINARY
- Received message : is not WSMsgType.TEXT
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/c49298eb45578a92.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/worker.py:83
self.loop.close()
sys.exit(self.exit_code)
async def _run(self) -> None:
runner = None
if isinstance(self.wsgi, Application):
app = self.wsgi
elif inspect.iscoroutinefunction(self.wsgi) or (
sys.version_info < (3, 14) and asyncio.iscoroutinefunction(self.wsgi) # type: ignore[deprecated]
):
wsgi = await self.wsgi()
if isinstance(wsgi, web.AppRunner):
runner = wsgi
app = runner.app
else:
app = wsgi
else:
raise RuntimeError(
"wsgi app should be either Application or "
f"async function returning Application, got {self.wsgi}"
)
if runner is None:
access_log = self.log.access_log if self.cfg.accesslog else None
runner = web.AppRunner(
app,
logger=self.log,
keepalive_timeout=self.cfg.keepalive,
access_log=access_log,
access_log_format=self._get_valid_log_format(
self.cfg.access_log_format
),
shutdown_timeout=self.cfg.graceful_timeout / 100 * 95,
)
await runner.setup()
View on GitHub (pinned to d041d4d0fd)