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
- Perform all state setup in an on_startup handler or cleanup_ctx before the app starts serving.
- For per-request mutable state, use request['key'] (the Request storage) instead of app['key'].
- 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
- Set all application state in on_startup or cleanup_ctx, never in handlers.
- Store mutable containers at startup and mutate their contents at runtime.
- Use request['key'] for per-request state.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Already started
- Cannot add frozen application
- Cannot add sub application to frozen application
- Inheritance class from web.Application is forbidden
- Call .prepare() first
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)