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

  1. Register all sub-applications and routes before calling web.run_app() or AppRunner.setup().
  2. If reconfiguration is needed, create a new Application instance rather than mutating a frozen one.
  3. 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

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


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 = app

View on GitHub (pinned to d041d4d0fd)