reflex-dev/reflex · error · ImmutableStateError
The state is already mutable. Do not nest `async with self`
Error message
The state is already mutable. Do not nest `async with self` blocks.
What it means
Inside a background (@rx.event(background=True)) task, state mutability is guarded by an async context lock held by the current task. Re-entering `async with self` while the same task already holds the lock is a programming error (the state is already mutable), so ImmutableStateError is raised instead of deadlocking on a non-reentrant lock.
Source
Thrown at reflex/istate/proxy.py:148
parent_state = (
await self._self_parent_state_proxy.__aenter__()
).__wrapped__
super().__setattr__(
"__wrapped__",
await parent_state.get_state(
State.get_class_substate(self._self_substate_path)
),
)
self._self_entered_context = True
return self
current_task = asyncio.current_task()
if (
self._self_actx_lock.locked()
and current_task == self._self_actx_lock_holder
):
msg = "The state is already mutable. Do not nest `async with self` blocks."
raise ImmutableStateError(msg)
ctx = EventContext.get()
await self._self_actx_lock.acquire()
try:
self._self_actx_lock_holder = current_task
self._self_actx = ctx.state_manager.modify_state_with_links(
token=self._self_substate_token, event=self._self_event
)
mutable_state = await self._self_actx.__aenter__()
self._self_mutable = True
self._self_entered_context = True
super().__setattr__(
"__wrapped__", mutable_state.get_substate(self._self_substate_path)
)
except (Exception, asyncio.CancelledError):
# Restore the proxy to a consistent state since __aexit__ will not be called when __aenter__ raises.
await self.__aexit__(*sys.exc_info())View on GitHub (pinned to 45b8ed5ab7)
Solutions
- Flatten to a single `async with self` block per task and do all mutations inside it
- Make helpers take the already-mutable state (call them inside the context) rather than opening their own
- If separate mutation windows are genuinely needed, exit the first context before entering the second
Example fix
# before
async with self:
self.a = 1
async with self: # ImmutableStateError
self.b = 2
# after
async with self:
self.a = 1
self.b = 2 Defensive patterns
Strategy: type-guard
Prevention
- One `async with self` block per background task; helpers receive already-mutable state
- Never write helpers that open their own context when called from within one
- Code-review background handlers for nested context entries
When it happens
Trigger: Nesting `async with self:` blocks inside one background event handler, e.g. calling a helper that also does `async with self` from inside an existing `async with self` block in the same task.
Common situations: Refactoring background handlers into helper functions that each open their own context; copy-pasting a mutation block into an already-mutable region; awaiting a utility coroutine that internally enters the context.
Related errors
- Background task StateProxy is immutable outside of a context
- @rx.event(background=True) is required for upload_files_chun
- Background task must use `async with self` to modify state.
- Cannot suppress RuntimeError exceptions which may be raised
- ChildrenTypeError(component=cls.__name__, child=child)
AI-assisted analysis of reflex-dev/reflex@45b8ed5ab7 (2026-08-28).
Data as JSON: /api/errors/28de64e72132345c.
Report an issue: GitHub.