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 is frozen (during startup or when itself added as a subapp), its router and subapp list are immutable, so adding a new subapp would not be reflected in the live routing table and could violate the freeze contract. The check uses self.frozen.

Solutions

  1. Register all subapps before starting the server, i.e. at module load time or in an on_startup handler that runs before freeze.
  2. If dynamic mounting is required, restructure so subapps are known at startup, or rebuild and restart the app.
  3. Check app.frozen before calling add_subapp and surface a clear error.

Example fix

# before
async def late_startup(app):
    app.add_subapp('/late', sub)  # parent already frozen
app.on_startup.append(late_startup)

# after
app.add_subapp('/late', sub)  # register at module load, before run_app
Defensive patterns

Strategy: validation

Validate before calling

if app.frozen:
    raise RuntimeError('cannot add subapp after startup')
app.add_subapp(prefix, subapp)

Type guard

def app_accepts_subapps(app) -> bool:
    return not getattr(app, 'frozen', False)

Prevention

When it happens

Trigger: Calling app.add_subapp(...) after web.run_app has started, or inside an on_startup handler that runs after freeze, or on an app that was already attached as a subapp elsewhere.

Common situations: Dynamically registering sub-apps from startup hooks that fire post-freeze; attempting to add routes/subapps from within a request handler; ordering on_startup callbacks such that add_subapp runs after the app freeze step.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/694103246839f0b9. Report an issue: GitHub.

Appendix: 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 d041d4d0fd)