aio-libs/aiohttp · error · RuntimeError
Cannot add frozen application
Error message
Cannot add frozen application
What it means
Raised by Application._add_subapp when the subapp being attached is already frozen. A subapp is frozen by aiohttp as part of mounting (see _add_subapp calls subapp.pre_freeze()), so passing an already-frozen subapp means it was either previously mounted elsewhere or explicitly frozen, and re-mounting would double-freeze or share mutable routing state. The check uses subapp.frozen.
Solutions
- Create a fresh Application instance for each mount; do not reuse a subapp across parents.
- If you need the same routes under multiple prefixes, use a factory function that builds a new Application each call.
- Ensure add_subapp is called exactly once per subapp instance.
Example fix
# before
shared = web.Application()
app.add_subapp('/a', shared)
app.add_subapp('/b', shared) # raises: already frozen
# after
def make_sub():
sub = web.Application()
sub.add_routes(...)
return sub
app.add_subapp('/a', make_sub())
app.add_subapp('/b', make_sub()) Defensive patterns
Strategy: validation
Validate before calling
if subapp.frozen:
raise RuntimeError('subapp already mounted; create a fresh instance')
app.add_subapp(prefix, subapp) Type guard
def subapp_mountable(subapp) -> bool:
return not getattr(subapp, 'frozen', False) Prevention
- Use a factory to build a new Application for each mount.
- Never reuse one subapp instance under multiple parents.
- Call add_subapp exactly once per subapp instance.
When it happens
Trigger: Passing the same subapp instance to add_subapp twice (mounting it under two parents); mounting a subapp that was already attached as a child of another app; manually freezing a subapp then trying to mount it.
Common situations: Reusing a single Application object across multiple parents for code sharing; building a subapp once and registering it in a loop; refactoring that moves a subapp without recreating it.
Related errors
- Cannot add sub application to frozen application
- Changing state of started or joined application is forbidden
- Prefix cannot be empty
- Prefix must be str
- Already started
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/9d9005ec6bae233a.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_app.py:290
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)
def add_routes(self, routes: Iterable[AbstractRouteDef]) -> list[AbstractRoute]:View on GitHub (pinned to d041d4d0fd)