aio-libs/aiohttp · error · RuntimeError
Cannot add sub application to frozen application
Error message
Cannot add sub application to frozen application
What it means
Raised by Application._add_subapp when the parent application is already frozen. Once an app has started serving (or was explicitly frozen), its router is immutable because routes are matched against live requests; adding a subapp would alter routing mid-flight. The fix is structural: register subapps before startup, or restart the application with the new topology.
Source
Thrown at aiohttp/web_app.py:288
reg_handler("on_startup")
reg_handler("on_shutdown")
reg_handler("on_cleanup")
def add_subapp(self, prefix: str, subapp: "Application") -> PrefixedSubAppResource:
if not isinstance(prefix, str):
raise TypeError("Prefix must be str")
prefix = prefix.rstrip("/")
if not prefix:
raise ValueError("Prefix cannot be empty")
factory = partial(PrefixedSubAppResource, prefix, subapp)
return self._add_subapp(factory, subapp)
def _add_subapp(
self, resource_factory: Callable[[], _Resource], subapp: "Application"
) -> _Resource:
if self.frozen:
raise RuntimeError("Cannot add sub application to frozen application")
if subapp.frozen:
raise RuntimeError("Cannot add frozen application")
resource = resource_factory()
self.router.register_resource(resource)
self._reg_subapp_signals(subapp)
self._subapps.append(subapp)
subapp.pre_freeze()
return resource
def add_domain(self, domain: str, subapp: "Application") -> MatchedSubAppResource:
if not isinstance(domain, str):
raise TypeError("Domain must be str")
elif "*" in domain:
rule: Domain = MaskDomain(domain)
else:
rule = Domain(domain)
factory = partial(MatchedSubAppResource, rule, subapp)
return self._add_subapp(factory, subapp)View on GitHub (pinned to c0ef574e29)
Solutions
- Register all subapps before run_app()/AppRunner.setup() — typically at module load.
- For dynamic tenants, use a single catch-all route whose handler dispatches, instead of mutating the router.
- If topology must change, tear down the runner and start a new Application.
- Move registration into Application construction or a setup() called before serving.
Example fix
// before
async def handler(request):
request.app.add_subapp('/tenant', tenant_app) # frozen -> RuntimeError
// after
app.add_subapp('/tenant', tenant_app) # at construction time
web.run_app(app) Defensive patterns
Strategy: validation
Validate before calling
def safe_add_subapp(parent, prefix, subapp):
if getattr(parent, 'frozen', False):
raise RuntimeError('parent is frozen; add subapps before startup')
return parent.add_subapp(prefix, subapp) Try / catch
try:
parent.add_subapp(prefix, subapp)
except RuntimeError as e:
if 'frozen application' in str(e):
# rebuild topology: tear down runner, build new app
...
raise Prevention
- Mount all subapps at construction time, before run_app().
- For dynamic tenants, dispatch inside a handler rather than mutating the router.
- Rebuild the Application if topology must change at runtime.
When it happens
Trigger: Calling parent_app.add_subapp(...) from inside a request handler or after run_app() has started; adding a subapp in on_startup (which fires during the startup sequence, after pre_freeze); dynamic plugin registration triggered by a request.
Common situations: Hot-reloading plugins in a long-running server; lazy mounting in tests that reuse a started app; misordered on_startup handlers where registration runs after freeze; multi-tenant routers added on first tenant.
Related errors
- Cannot add frozen application
- Changing state of started or joined application is forbidden
- Prefix must be str
- Prefix cannot be empty
- Domain must be str
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/694103246839f0b9.json.
Report an issue: GitHub.