{"record":{"id":"c663861a1c9b64a7","repo":"aio-libs/aiohttp","slug":"cannot-register-a-resource-into-frozen-router","errorCode":null,"errorMessage":"Cannot register a resource into frozen router.","messagePattern":"Cannot register a resource into frozen router\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":1045,"sourceCode":"\n    def __getitem__(self, name: str) -> AbstractResource:\n        return self._named_resources[name]\n\n    def resources(self) -> ResourcesView:\n        return ResourcesView(self._resources)\n\n    def routes(self) -> RoutesView:\n        return RoutesView(self._resources)\n\n    def named_resources(self) -> Mapping[str, AbstractResource]:\n        return MappingProxyType(self._named_resources)\n\n    def register_resource(self, resource: AbstractResource) -> None:\n        assert isinstance(\n            resource, AbstractResource\n        ), f\"Instance of AbstractResource class is required, got {resource!r}\"\n        if self.frozen:\n            raise RuntimeError(\"Cannot register a resource into frozen router.\")\n\n        name = resource.name\n\n        if name is not None:\n            parts = self.NAME_SPLIT_RE.split(name)\n            for part in parts:\n                if keyword.iskeyword(part):\n                    raise ValueError(\n                        f\"Incorrect route name {name!r}, \"\n                        \"python keywords cannot be used \"\n                        \"for route name\"\n                    )\n                if not part.isidentifier():\n                    raise ValueError(\n                        f\"Incorrect route name {name!r}, \"\n                        \"the name should be a sequence of \"\n                        \"python identifiers separated \"\n                        \"by dash, dot or column\"","sourceCodeStart":1027,"sourceCodeEnd":1063,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L1027-L1063","documentation":"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).","triggerScenarios":"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().","commonSituations":"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.","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."],"exampleFix":"// before — inside a handler, app already frozen\nasync def add_new_route(request):\n    app.router.add_get('/new', new_handler)\n// after — register up front, dispatch dynamically\nasync def dispatcher(request):\n    if request.path == '/new':\n        return await new_handler(request)\napp.router.add_get('/{tail:.*}', dispatcher)","handlingStrategy":"validation","validationCode":"def register_safe(router, *args, **kw):\n    if router.frozen:\n        raise RuntimeError('cannot mutate frozen router at runtime; register before startup')\n    return router.add_route(*args, **kw)\n\n# or check up front:\nif app.router.frozen:\n    log.error('router already frozen — register routes before run_app')","typeGuard":"def router_is_mutable(app) -> bool:\n    return not app.router.frozen","tryCatchPattern":"try:\n    app.router.add_route('GET', '/x', h)\nexcept RuntimeError as e:\n    if 'frozen' in str(e):\n        log.error('cannot add routes after startup; use a dynamic dispatcher route instead')\n    raise","preventionTips":["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."],"tags":["url-dispatcher","frozen-router","lifecycle","runtime"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}