Skip to content

Fix config reload state imports (#7028) - #7035

Open
harsh21234i wants to merge 1 commit into
reflex-dev:mainfrom
harsh21234i:fix/config-reload-state-imports
Open

Fix config reload state imports (#7028)#7035
harsh21234i wants to merge 1 commit into
reflex-dev:mainfrom
harsh21234i:fix/config-reload-state-imports

Conversation

@harsh21234i

@harsh21234i harsh21234i commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

• ## Summary
Fixes: #7028
Fix reload_config() raising StateValueError when rxconfig.py imports a
module that defines a rx.State class.

Changes

  • Preserve project-local dependencies during reloads within the same
    RegistrationContext.

  • Continue evicting dependencies for new contexts or different project
    roots.

  • Added regression tests for state reloads, fresh contexts, and cross-
    project isolation.

  • Added a reflex-base bugfix changelog fragment.

Testing

  • Focused regression tests: 3 passed
  • Ruff check and formatting: passed

Review in cubic

@harsh21234i
harsh21234i requested a review from a team as a code owner September 2, 2026 17:07
@greptile-apps

greptile-apps Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR prevents duplicate state registration when reload_config() reimports an rxconfig.py dependency within the same registration context.

  • Tracks the project root associated with recorded config dependencies.
  • Preserves dependencies for same-context reloads while evicting them for fresh contexts and project-root changes.
  • Adds regression coverage for state imports, fresh contexts, and cross-project isolation, plus a bugfix changelog fragment.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete blocking or independently actionable non-blocking issue identified.

The changed loader continues to reload rxconfig.py, preserves state-defining dependencies only in an existing same-root context, and still evicts dependencies for fresh contexts and project changes.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/config.py Adjusts dependency eviction according to registration-context freshness and project-root identity; no actionable defect was established.
tests/units/test_config.py Adds focused regression tests for retained state modules, fresh-context state registration, and same-named dependencies across projects.
packages/reflex-base/news/+config-reload-state-imports.bugfix.md Accurately documents the user-visible duplicate-state reload fix.

Reviews (1): Last reviewed commit: "Fix config reload state imports (#7028)" | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Sep 2, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 32 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing harsh21234i:fix/config-reload-state-imports (f01c755) with main (b9286f5)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/reflex-base/src/reflex_base/config.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/config.py:1034">
P1: When an older `RegistrationContext` is re-entered after another same-project context has loaded, `ctx._config is None` is false, so `_get_config` retains the globally cached dependency modules from the newer context. `rxconfig.py` then references state classes registered in the newer context while the active context retains its original classes, causing registry/runtime state mismatches; track dependency modules per context or otherwise restore the context-owned modules instead of using the global root-only cache.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

"""
ctx = RegistrationContext.ensure_context()
config = _get_config()
config = _get_config(reload_dependencies=ctx._config is None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When an older RegistrationContext is re-entered after another same-project context has loaded, ctx._config is None is false, so _get_config retains the globally cached dependency modules from the newer context. rxconfig.py then references state classes registered in the newer context while the active context retains its original classes, causing registry/runtime state mismatches; track dependency modules per context or otherwise restore the context-owned modules instead of using the global root-only cache.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/config.py, line 1034:

<comment>When an older `RegistrationContext` is re-entered after another same-project context has loaded, `ctx._config is None` is false, so `_get_config` retains the globally cached dependency modules from the newer context. `rxconfig.py` then references state classes registered in the newer context while the active context retains its original classes, causing registry/runtime state mismatches; track dependency modules per context or otherwise restore the context-owned modules instead of using the global root-only cache.</comment>

<file context>
@@ -1021,6 +1031,6 @@ def reload_config() -> Config:
     """
     ctx = RegistrationContext.ensure_context()
-    config = _get_config()
+    config = _get_config(reload_dependencies=ctx._config is None)
     ctx._set_config(config)
     return config
</file context>

@FarhanAliRaza FarhanAliRaza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested with the appmod.py / rxconfig.py pair from #7028.

On main, get_config() followed by reload_config() in one RegistrationContext raises StateValueError. The new regression test fails there with that error and passes on this branch. ruff, pyright, and tests/units/test_config.py pass on the branch.

I also ran the same reload on a forked context, the shape AppHarness uses. That still raises the same StateValueError. I removed the _config_module_deps_root comparison and re-ran the config tests to check the root guard. They all pass without it.

See the inline comments for the requested changes.

# before probing: find_spec answers from sys.modules, so modules
# left behind by another project directory would fake the existence
# check below.
# Always reload rxconfig, but retain its dependencies when reloading

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too long comments, explaining what is already can be seen in code. happens in many places.
we might want to clean up these.

"""
ctx = RegistrationContext.ensure_context()
config = _get_config()
config = _get_config(reload_dependencies=ctx._config is None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctx._config is None is the wrong signal. RegistrationContext.fork() copies base_states but resets _config to None. So a forked context evicts and re-imports the state module into a context that already holds the class, and the shadow check fires again.

Repro with the appmod.py / rxconfig.py pair from #7028:

with RegistrationContext() as ctx:
    c.get_config()
    forked = ctx.fork()
    tok = RegistrationContext.set(forked)
    c.reload_config()
    # StateValueError: The substate class 'appmod____my_state' has been defined multiple times.

This is the path AppHarness takes (reflex/testing.py:286: fork, then reload_config()), so the harness still crashes on such a project. Please key the decision on whether the current context already holds the states those modules registered, not on whether it has a cached config. Add the fork case to the tests.

for dep in _config_module_deps:
sys.modules.pop(dep, None)
_config_module_deps.clear()
if reload_dependencies or _config_module_deps_root != project_root:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _config_module_deps_root != project_root branch is not exercised. With the comparison removed, all 118 tests in tests/units/test_config.py still pass. test_get_config_evicts_dependencies_from_another_project calls _get_config() with the default reload_dependencies=True, so it never reaches this check.

Either drop _config_module_deps_root or add a test that reloads in one context after the cwd moved to a second project. Note that in that scenario a same-named state module would still hit the shadow error, so the guard may not buy anything.

)

assert reflex_base.config._get_config(first_project).app_name == "first"
assert reflex_base.config._get_config(second_project).app_name == "second"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both calls use reload_dependencies=True, so this passes on main too (after the fixture is adjusted) and does not cover the new root check. To cover it, load first_project into a context, then call reload_config() from second_project in the same context.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants