Skip to content

Reloading a config whose rxconfig.py imports a state-defining module raises StateValueError #7028

Description

@masenf

Describe the bug

If rxconfig.py imports a project-local module that defines an rx.State subclass, reloading the config in the same RegistrationContext crashes:

StateValueError: The substate class 'appmod____my_state' has been defined multiple times.
Shadowing substate classes is not allowed.

_get_config (packages/reflex-base/src/reflex_base/config.py) drops every recorded project-local dependency from sys.modules before each load, so the next load re-imports them and re-executes any rx.State class body they contain. The second class object collides with the first, which is still registered as a substate of its parent in the current context, and the shadowing check in State.__init_subclass__ (reflex/state.py:583) fires.

This is deterministic, not a race — it happens on every reload for such a project.

To Reproduce

Two files in a project directory:

# appmod.py
import reflex as rx


class MyState(rx.State):
    value: str = ""
# rxconfig.py
import appmod  # noqa: F401
import reflex as rx

config = rx.Config(app_name="statereload")

Then:

import reflex_base.config as c
from reflex_base.registry import RegistrationContext

with RegistrationContext():
    c.get_config()
    c.reload_config()  # StateValueError

get_config(reload=True) fails the same way, since it delegates to reload_config().

Scope of the failure:

scenario result
two separate RegistrationContexts, one load each works — each context has its own substate registry
reload_config() / get_config(reload=True) in one context StateValueError

Expected behavior

Reloading the config re-reads rxconfig.py from disk and returns a fresh Config, without redefining states that the previous load already registered.

Specifics (please complete the following information):

  • Python Version: 3.14.7
  • Reflex Version: main @ 812bb47 (reflex-base 0.9.10)
  • OS: Linux

Additional context

The eviction exists so that two different project directories don't reuse each other's modules — that part works and should be preserved; interleaved loads across two projects with same-named helper modules each correctly resolve their own copy.

Two directions for a fix:

  1. Scope the eviction to actual project changes — only drop the recorded deps when the project root differs from the one they were recorded under. rxconfig.py itself is still evicted every load, so a reload still re-reads it. Small and contained, but a same-root reload then stops picking up edits to helper modules imported by rxconfig.py (edits to rxconfig.py itself are still picked up).
  2. Unregister the states before re-importing. This is what reload_state_module (reflex/state.py) already does for hot reload, but it lives in the reflex package and reflex_base imports nothing from reflex, so it isn't reachable from config.py today. Doing it properly likely means giving RegistrationContext an unregister-by-module capability.

Noticed while reviewing #6933; it is unrelated to that PR's sys.path changes and reproduces identically on main.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions