aio-libs/aiohttp · error · RuntimeError
Cannot change apps stack after .freeze() call
Error message
Cannot change apps stack after .freeze() call
What it means
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.
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.
Example fix
// before
web.run_app(app)
app.router.add_subapp('/api', sub_app) # too late, app is frozen
// after
app.router.add_subapp('/api', sub_app) # add before run
web.run_app(app) Defensive patterns
Strategy: validation
Validate before calling
def add_subapp_safely(app, prefix, sub_app):
if getattr(app, '_frozen', False) or app.router.frozen:
raise RuntimeError('Application is frozen; create a new one')
return app.router.add_subapp(prefix, sub_app) Type guard
def is_frozen(app) -> bool:
return getattr(app, '_frozen', False) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- .url_for() is not supported by sub-application root
- Added route will never be executed, method
- Already started
- Bad pattern
- Call .prepare() first
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/8f94012d3a59e87e.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_urldispatcher.py:249
@property
def expect_handler(self) -> _ExpectHandler:
return self._route.handle_expect_header
@property
def http_exception(self) -> HTTPException | None:
return None
def get_info(self) -> _InfoDict: # type: ignore[override]
return self._route.get_info()
@property
def apps(self) -> tuple["Application", ...]:
return tuple(self._apps)
def add_app(self, app: "Application") -> None:
if self._frozen:
raise RuntimeError("Cannot change apps stack after .freeze() call")
if self._current_app is None:
self._current_app = app
self._apps.insert(0, app)
@property
def current_app(self) -> "Application":
app = self._current_app
assert app is not None
return app
@current_app.setter
def current_app(self, app: "Application") -> None:
if DEBUG:
if app not in self._apps:
raise RuntimeError(
f"Expected one of the following apps {self._apps!r}, got {app!r}"
)
self._current_app = appView on GitHub (pinned to d041d4d0fd)