aio-libs/aiohttp · error · RuntimeError
Cannot change apps stack after .freeze() call
Error message
Cannot change apps stack after .freeze() call
What it means
Raised as RuntimeError by UrlMappingMatchInfo.add_app when an application is pushed onto the match-info's app stack after freeze() was called. The app stack records the chain of sub-applications traversed during routing; once frozen (after startup), it is immutable, so late additions indicate routing happening after the app lifecycle entered its frozen phase.
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 c0ef574e29)
Solutions
- Ensure sub-applications are added via app.add_sub_app(prefix, sub_app) BEFORE app.freeze()/startup, not during request handling.
- Do not call match_info.add_app(...) in custom resource resolve() implementations after the app is frozen.
- If using a custom AbstractResource, return a fresh UrlMappingMatchInfo rather than reusing a frozen one.
- Verify you are not re-freezing or re-starting an already-started Application.
Example fix
# before (adding sub-app after startup)
runner = web.AppRunner(app)
await runner.setup()
app.add_sub_app('/sub', sub_app) # too late
# after
app.add_sub_app('/sub', sub_app)
runner = web.AppRunner(app)
await runner.setup() Defensive patterns
Strategy: validation
Validate before calling
# Prevent by structuring setup: never add sub-apps after runner.setup()
def setup_app(app, sub_apps):
for prefix, sub in sub_apps:
app.add_sub_app(prefix, sub) # before any freeze/startup
return app Try / catch
try:
app.add_sub_app(prefix, sub_app)
except RuntimeError as e:
if 'freeze' in str(e):
raise RuntimeError("App already frozen; add sub-apps before startup") from e
raise Prevention
- Add all sub-applications before calling runner.setup()/startup.
- Do not reuse frozen Applications across setups.
- Treat add_sub_app after startup as a configuration bug.
When it happens
Trigger: Internally triggered when a sub-application resource tries to add_app after the match info is frozen — typically a sign of routing against an Application whose cleanup/startup re-entered resolution, or a custom AbstractResource that resolves after freeze. Not normally raised by user code directly.
Common situations: Custom router/resource subclasses that call add_app outside the normal request routing window; using add_sub_app after app.freeze()/startup; re-using a frozen Application's match info; bugs in middleware that re-resolve routes.
Related errors
- Expected one of the following apps {self._apps!r}, got {app!
- Added route will never be executed, method {route.method} is
- .url_for() is not supported by sub-application root
- {method} is not allowed HTTP method
- Only async functions are allowed as web-handlers, got {handl
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/8f94012d3a59e87e.json.
Report an issue: GitHub.