{"record":{"id":"8f94012d3a59e87e","repo":"aio-libs/aiohttp","slug":"cannot-change-apps-stack-after-freeze-call","errorCode":null,"errorMessage":"Cannot change apps stack after .freeze() call","messagePattern":"Cannot change apps stack after \\.freeze\\(\\) call","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_urldispatcher.py","lineNumber":249,"sourceCode":"\n    @property\n    def expect_handler(self) -> _ExpectHandler:\n        return self._route.handle_expect_header\n\n    @property\n    def http_exception(self) -> HTTPException | None:\n        return None\n\n    def get_info(self) -> _InfoDict:  # type: ignore[override]\n        return self._route.get_info()\n\n    @property\n    def apps(self) -> tuple[\"Application\", ...]:\n        return tuple(self._apps)\n\n    def add_app(self, app: \"Application\") -> None:\n        if self._frozen:\n            raise RuntimeError(\"Cannot change apps stack after .freeze() call\")\n        if self._current_app is None:\n            self._current_app = app\n        self._apps.insert(0, app)\n\n    @property\n    def current_app(self) -> \"Application\":\n        app = self._current_app\n        assert app is not None\n        return app\n\n    @current_app.setter\n    def current_app(self, app: \"Application\") -> None:\n        if DEBUG:\n            if app not in self._apps:\n                raise RuntimeError(\n                    f\"Expected one of the following apps {self._apps!r}, got {app!r}\"\n                )\n        self._current_app = app","sourceCodeStart":231,"sourceCodeEnd":267,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_urldispatcher.py#L231-L267","documentation":"AbstractResource.add_app maintains a stack of applications for nested/sub-app resolution. Once freeze() has been called on the resource (typically when Application.freeze() cascades through the router on startup, cleanup, or after adding a sub-app), the resource is immutable. Calling add_app afterwards raises RuntimeError because mutating the app stack post-freeze would leave the router in an inconsistent, partially-indexed state.","triggerScenarios":"Calling resource.add_app(app) after Application.freeze() has run — which happens automatically during runner startup (AppRunner / web.run_app), during app.cleanup(), or after an explicit app.freeze(). Sub-applications added via add_subapp also trigger freezing of the parent's resources.","commonSituations":"Trying to add a sub-application or middleware after the server has started; calling web.run_app() and then mutating the app/router in a background task; test setup that reuses a frozen Application across test cases; reconfiguration during a hot-reload attempt.","solutions":["Register all sub-applications and routes before calling web.run_app() or AppRunner.setup().","If reconfiguration is needed, create a new Application instance rather than mutating a frozen one.","In tests, build a fresh Application per test rather than reusing one that has been started."],"exampleFix":"// before\nweb.run_app(app)\napp.router.add_subapp('/api', sub_app)  # too late, app is frozen\n// after\napp.router.add_subapp('/api', sub_app)  # add before run\nweb.run_app(app)","handlingStrategy":"validation","validationCode":"def add_subapp_safely(app, prefix, sub_app):\n    if getattr(app, '_frozen', False) or app.router.frozen:\n        raise RuntimeError('Application is frozen; create a new one')\n    return app.router.add_subapp(prefix, sub_app)","typeGuard":"def is_frozen(app) -> bool:\n    return getattr(app, '_frozen', False)","tryCatchPattern":null,"preventionTips":["Register all routes and sub-apps before calling web.run_app / runner.setup.","Treat Application as immutable once the server starts; rebuild it to reconfigure.","In tests, use a pytest fixture that yields a fresh Application per test."],"tags":["routing","lifecycle","freeze","sub-app"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}