aio-libs/aiohttp · error · RuntimeError

Changing state of started or joined application is forbidden

Error message

Changing state of started or joined application is forbidden

What it means

Raised by Application._check_frozen (invoked from __setitem__ and other state mutators) when code tries to change application state after the app has been frozen. An Application is frozen during startup (and when added as a subapp), because mutating state concurrently with request handling is unsafe. This enforces immutability of the application object once the server is live.

Solutions

  1. Perform all state setup in an on_startup handler or cleanup_ctx before the app starts serving.
  2. For per-request mutable state, use request['key'] (the Request storage) instead of app['key'].
  3. If you need shared mutable runtime state, store a mutable container (dict/obj) once at startup and mutate its contents, not the app mapping.

Example fix

# before
async def handler(request):
    request.app['cache'] = {}  # raises once frozen

# after
async def init_cache(app):
    app['cache'] = {}
app.on_startup.append(init_cache)
# handlers then read app['cache'] and mutate its contents
Defensive patterns

Strategy: validation

Validate before calling

if app.frozen:
    raise RuntimeError('cannot mutate app after startup; use on_startup')
app['key'] = value

Type guard

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

Try / catch

try:
    app['key'] = value
except RuntimeError as e:
    if 'frozen' in str(e).lower() or 'forbidden' in str(e):
        # defer to a mutable container set up at startup
        app['runtime']['key'] = value
    else:
        raise

Prevention

When it happens

Trigger: Calling app['key'] = value (or other state-changing operations) inside a request handler, an on_startup handler that runs after freeze, or after web.run_app has started serving.

Common situations: Lazily initializing shared resources in a handler instead of on_startup; trying to register routes or middlewares after startup; mutating app state from a background task spawned during serving.

Understand the failure class

Related errors


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

Appendix: source

Thrown at aiohttp/web_app.py:155

        )

    # MutableMapping API

    def __eq__(self, other: object) -> bool:
        return self is other

    @overload  # type: ignore[override]
    def __getitem__(self, key: AppKey[_T]) -> _T: ...

    @overload
    def __getitem__(self, key: str) -> Any: ...

    def __getitem__(self, key: str | AppKey[_T]) -> Any:
        return self._state[key]

    def _check_frozen(self) -> None:
        if self._frozen:
            raise RuntimeError(
                "Changing state of started or joined application is forbidden"
            )

    @overload  # type: ignore[override]
    def __setitem__(self, key: AppKey[_T], value: _T) -> None: ...

    @overload
    def __setitem__(self, key: str, value: Any) -> None: ...

    def __setitem__(self, key: str | AppKey[_T], value: Any) -> None:
        self._check_frozen()
        if not isinstance(key, AppKey):
            warnings.warn(
                "It is recommended to use web.AppKey instances for keys.\n"
                + "https://docs.aiohttp.org/en/stable/web_advanced.html"
                + "#application-s-config",
                category=NotAppKeyWarning,
                stacklevel=2,

View on GitHub (pinned to d041d4d0fd)