Describe the bug
Since #7136, rx.State's metaclass is reflex.istate.validation._StateMeta (a subclass of BaseStateMeta) instead of BaseStateMeta itself (reflex/state.py:629 class BaseState(EvenMoreBasicBaseState, metaclass=_StateMeta), _StateMeta defined at reflex/istate/validation.py:90). Any downstream metaclass written as class MyMeta(BaseStateMeta) — the only spelling that worked through 0.9.11.post1, and BaseStateMeta is exported in reflex_base.vars.__all__ — can no longer be used on a State subclass, because two sibling subclasses of BaseStateMeta cannot both be the metaclass of one class:
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
reflex-enterprise 0.9.5 does exactly this (reflex_enterprise/auth/oidc/state.py:347 class OIDCCookieMeta(BaseStateMeta), used at line 381 by OIDCAuthState). Because MCPPlugin.post_compile and EventHandlerAPIPlugin.post_compile import the OIDC module, every app using rxe.AuthPlugin(), rxe.MCPPlugin() or rxe.EventHandlerAPIPlugin() dies at startup on 0.9.12a1 — the shipped demos/oidc and demos/tickets included — while the vite frontend keeps serving 200, so the page loads and shows "Cannot connect to server: websocket error". reflex-enterprise 0.9.5 pins reflex[db]>=0.9.6 with no upper bound, so a routine pip install -U reflex after 0.9.12 ships would break deployed apps. The metaclass change is not mentioned in the changelog.
To Reproduce
Framework only, no enterprise needed (uv pip install --prerelease=allow 'reflex==0.9.12a1'):
import reflex as rx
from reflex.vars import BaseStateMeta
class CookieMeta(BaseStateMeta):
def __new__(mcs, name, bases, attrs, **kwargs):
attrs.setdefault("added_by_meta", "yes")
return super().__new__(mcs, name, bases, attrs, **kwargs)
class S(rx.State, mixin=True, metaclass=CookieMeta): # fails without mixin=True too
x: int = 0
- 0.9.12a1:
TypeError: metaclass conflict ...
- 0.9.11.post1: works (
type(rx.State) is BaseStateMeta)
class M(type(rx.State)) works on both versions, but no published downstream code spells it that way.
Enterprise: in a venv with reflex 0.9.12a1 + reflex-enterprise[mcp]==0.9.5, python -c "import reflex_enterprise.auth.oidc.state" raises the same TypeError, and reflex run on an app with only rxe.MCPPlugin() never binds the backend port (22 other enterprise modules import fine; 23/23 import on 0.9.11.post1).
Expected behavior
A metaclass derived from BaseStateMeta keeps working on State subclasses, as it did through 0.9.11.post1 — or the change is announced as breaking, with the type(rx.State) spelling documented.
Suggested fix shape
Perform the #7136 reserved-name validation inside BaseStateMeta.__new__ (guarded on "a base is a BaseState") so rx.State keeps BaseStateMeta as its metaclass, or make the validating metaclass compose with sibling BaseStateMeta subclasses. Regression test: the snippet above must construct. reflex-enterprise can independently switch to class OIDCCookieMeta(type(rx.State)), but that does not rescue the already-published 0.9.5 against a released 0.9.12.
Specifics (please complete the following information):
- Python Version: 3.11.15 (also reproduced on 3.10 and 3.12–3.15)
- Reflex Version: 0.9.12a1 / reflex-base 0.9.12a1 (regression vs 0.9.11.post1)
- OS: Linux (Ubuntu 24.04 container)
Additional context
Found by the 0.9.12a1 pre-release QA campaign (FINDING-001), reproduced by the orchestrator, two explorer agents and an adversarial verifier. Evidence and scripts: prerelease-testing/2026-09-18-v0.9.12a1/ on branch claude/reflex-prerelease-testing-t0sd90 — orch_probes/metaclass_probe.py, ent_mcp_oidc/NOTES.md, ent_map_dnd_flow_mantine/logs/tickets_new_dev.TRACEBACK.txt, and FINDINGS.md.
Describe the bug
Since #7136,
rx.State's metaclass isreflex.istate.validation._StateMeta(a subclass ofBaseStateMeta) instead ofBaseStateMetaitself (reflex/state.py:629class BaseState(EvenMoreBasicBaseState, metaclass=_StateMeta),_StateMetadefined atreflex/istate/validation.py:90). Any downstream metaclass written asclass MyMeta(BaseStateMeta)— the only spelling that worked through 0.9.11.post1, andBaseStateMetais exported inreflex_base.vars.__all__— can no longer be used on a State subclass, because two sibling subclasses ofBaseStateMetacannot both be the metaclass of one class:reflex-enterprise 0.9.5 does exactly this (
reflex_enterprise/auth/oidc/state.py:347class OIDCCookieMeta(BaseStateMeta), used at line 381 byOIDCAuthState). BecauseMCPPlugin.post_compileandEventHandlerAPIPlugin.post_compileimport the OIDC module, every app usingrxe.AuthPlugin(),rxe.MCPPlugin()orrxe.EventHandlerAPIPlugin()dies at startup on 0.9.12a1 — the shippeddemos/oidcanddemos/ticketsincluded — while the vite frontend keeps serving 200, so the page loads and shows "Cannot connect to server: websocket error". reflex-enterprise 0.9.5 pinsreflex[db]>=0.9.6with no upper bound, so a routinepip install -U reflexafter 0.9.12 ships would break deployed apps. The metaclass change is not mentioned in the changelog.To Reproduce
Framework only, no enterprise needed (
uv pip install --prerelease=allow 'reflex==0.9.12a1'):TypeError: metaclass conflict ...type(rx.State) is BaseStateMeta)class M(type(rx.State))works on both versions, but no published downstream code spells it that way.Enterprise: in a venv with reflex 0.9.12a1 +
reflex-enterprise[mcp]==0.9.5,python -c "import reflex_enterprise.auth.oidc.state"raises the same TypeError, andreflex runon an app with onlyrxe.MCPPlugin()never binds the backend port (22 other enterprise modules import fine; 23/23 import on 0.9.11.post1).Expected behavior
A metaclass derived from
BaseStateMetakeeps working on State subclasses, as it did through 0.9.11.post1 — or the change is announced as breaking, with thetype(rx.State)spelling documented.Suggested fix shape
Perform the #7136 reserved-name validation inside
BaseStateMeta.__new__(guarded on "a base is a BaseState") sorx.StatekeepsBaseStateMetaas its metaclass, or make the validating metaclass compose with siblingBaseStateMetasubclasses. Regression test: the snippet above must construct. reflex-enterprise can independently switch toclass OIDCCookieMeta(type(rx.State)), but that does not rescue the already-published 0.9.5 against a released 0.9.12.Specifics (please complete the following information):
Additional context
Found by the 0.9.12a1 pre-release QA campaign (FINDING-001), reproduced by the orchestrator, two explorer agents and an adversarial verifier. Evidence and scripts:
prerelease-testing/2026-09-18-v0.9.12a1/on branchclaude/reflex-prerelease-testing-t0sd90—orch_probes/metaclass_probe.py,ent_mcp_oidc/NOTES.md,ent_map_dnd_flow_mantine/logs/tickets_new_dev.TRACEBACK.txt, andFINDINGS.md.