aio-libs/aiohttp · error · RuntimeError
Cannot register a resource into frozen router.
Error message
Cannot register a resource into frozen router.
What it means
UrlDispatcher.freeze() marks the router immutable so aiohttp can safely share it across worker tasks without synchronization. After freeze(), any call that mutates the resource list — add_resource(), add_route(), add_get()/add_post()/etc., register_resource() — raises RuntimeError. The router is frozen automatically when the Application starts (during Application.startup / the on_startup signal that freezes the app).
Solutions
- Register all routes before web.run_app(app) or before AppRunner.setup() — typically in the setup phase, not in handlers or signals.
- For dynamic dispatch at request time, use a single catch-all route whose handler does the routing, instead of mutating app.router.
- If truly dynamic routes are needed, build a new Application per change rather than mutating a frozen one.
Example fix
// before — inside a handler, app already frozen
async def add_new_route(request):
app.router.add_get('/new', new_handler)
// after — register up front, dispatch dynamically
async def dispatcher(request):
if request.path == '/new':
return await new_handler(request)
app.router.add_get('/{tail:.*}', dispatcher) Defensive patterns
Strategy: validation
Validate before calling
def register_safe(router, *args, **kw):
if router.frozen:
raise RuntimeError('cannot mutate frozen router at runtime; register before startup')
return router.add_route(*args, **kw)
# or check up front:
if app.router.frozen:
log.error('router already frozen — register routes before run_app') Type guard
def router_is_mutable(app) -> bool:
return not app.router.frozen Try / catch
try:
app.router.add_route('GET', '/x', h)
except RuntimeError as e:
if 'frozen' in str(e):
log.error('cannot add routes after startup; use a dynamic dispatcher route instead')
raise Prevention
- Register all routes before web.run_app() / AppRunner.setup().
- Never mutate app.router from within a request handler.
- For request-time dispatch, use a single catch-all handler that routes internally.
When it happens
Trigger: Calling app.router.add_route(...) inside a request handler or middleware (the app is already frozen by then); adding routes in an on_startup signal that runs after freezing; calling app.router.add_resource() after Application() has been run by web.AppRunner / web.run_app().
Common situations: Lazy route registration attempted at request time; plugin systems that try to register routes after the server has started; tests that reuse one Application across multiple test_server sessions and try to mutate routes between them.
Related errors
- Already started
- Call .prepare() first
- Call runner.setup() before making a site
- Cannot add frozen application
- Cannot add sub application to frozen application
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/c663861a1c9b64a7.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_urldispatcher.py:1045
def __getitem__(self, name: str) -> AbstractResource:
return self._named_resources[name]
def resources(self) -> ResourcesView:
return ResourcesView(self._resources)
def routes(self) -> RoutesView:
return RoutesView(self._resources)
def named_resources(self) -> Mapping[str, AbstractResource]:
return MappingProxyType(self._named_resources)
def register_resource(self, resource: AbstractResource) -> None:
assert isinstance(
resource, AbstractResource
), f"Instance of AbstractResource class is required, got {resource!r}"
if self.frozen:
raise RuntimeError("Cannot register a resource into frozen router.")
name = resource.name
if name is not None:
parts = self.NAME_SPLIT_RE.split(name)
for part in parts:
if keyword.iskeyword(part):
raise ValueError(
f"Incorrect route name {name!r}, "
"python keywords cannot be used "
"for route name"
)
if not part.isidentifier():
raise ValueError(
f"Incorrect route name {name!r}, "
"the name should be a sequence of "
"python identifiers separated "
"by dash, dot or column"View on GitHub (pinned to d041d4d0fd)