Skip to content

Validate reserved state names before registration - #7136

Merged
masenf merged 6 commits into
reflex-dev:mainfrom
FarhanAliRaza:fix-reserved-state-names
Sep 17, 2026
Merged

masenf merged 6 commits into
reflex-dev:mainfrom
FarhanAliRaza:fix-reserved-state-names

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

State vars named _get_was_touched or _update_was_touched can replace framework helpers and crash persistence or cleanup. Other collisions, including get_fields and dirty_vars, can break class creation or bookkeeping. This rejects reserved declarations before field collection and state initialization, and applies the same validation to dynamic vars, fields, event handlers, and route arguments.

Reserved names are derived once from BaseState and its framework bases. Ordinary vars, inherited user vars, Python protocols, and explicitly marked method overrides remain supported. Validation also checks state mixins, ordinary Python mixins, and inherited model fields without invoking descriptors.

This changes the default behavior. Existing apps can temporarily set REFLEX_STATE_ALLOW_RESERVED_NAMES=1 to retain legacy handling of conflicting vars with a deprecation warning until 1.0. The flag preserves the old behavior, including collision-related crashes; renaming conflicting declarations resolves them. Migration guidance and fragments for both affected packages are included.

Supersedes #7132 and the method-specific follow-up fix for #7135.

Closes #7091.
Closes #7135.

Validation:

  • All 35 new cases pass; on the unchanged base, the 33 regression cases fail and the two compatibility controls pass.

  • The final state suites pass all 312 tests.

  • Full unit suite: 8,626 passed, 18 skipped. The coverage gate exits nonzero at 71.34%, below the required 72%; the unchanged base also reports 71.34% with 8,591 passed and 18 skipped. The new validator has 97.59% coverage; state plus validation have 86.19%.

  • Full pyright reflex tests passes. All commit hooks pass, including Ruff, codespell, stub generation, Pyright, and ty.

  • Repository Ruff checks and formatting pass with the ignored ignore/ scratch tree excluded.

  • Followed CONTRIBUTING.md and added regression tests, migration documentation, and package news fragments.

  • Breaking change with a temporary compatibility opt-in.

  • Checked overlapping work: this is the centralized replacement for Call _get_was_touched through BaseState so a state var cannot shadow it #7132 and the narrow A backend var named _update_was_touched breaks state cleanup and persistence checks #7135 follow-up.

Review in cubic

@FarhanAliRaza
FarhanAliRaza requested review from a team and Alek99 as code owners September 14, 2026 11:47
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-14T11:52:51.895663Z f901a6b PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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.

No issues found across 8 files

Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

The PR is not yet safe to merge because it violates the repository’s required fallback and deprecation policy, and the previously reported router collision remains unresolved.

Findings

  1. P1 Router Remains Shadowable
  2. P2 Migration Fallback Removed

Summary

Introduces centralized validation that prevents state declarations and dynamic registrations from shadowing framework-owned names.

  • Adds metaclass-based validation for direct declarations, Python mixins, state mixins, and inherited model fields.
  • Applies validation to dynamically registered fields, variables, event handlers, and route arguments.
  • Adds regression tests and updates state documentation and release notes.
  • Removes the previously proposed compatibility flag despite the repository’s required deprecation policy.

Reviews (4) · Last reviewed commit: "refactor(state): compute the fast-path n..."

Comment thread reflex/istate/validation.py
@codspeed

codspeed Bot commented Sep 14, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 53 untouched benchmarks
⏩ 9 skipped benchmarks1


Comparing FarhanAliRaza:fix-reserved-state-names (3aa4c66) with main (59df268)

Open in CodSpeed

Footnotes

  1. 9 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.

No issues found across 8 files

Re-trigger cubic

…ten the validating metaclass

Set the deprecation version to the next release, fold the colliding name into
the deprecation key so each collision in a class is reported, move the
inherited-member check into a helper, and drop the unneeded override marker on
BaseState.add_field. The shadowed-touched-method tests from reflex-dev#7132 opt into the
legacy flag.
Reserved names are always rejected. The tests that constructed states with a
reserved backend var can no longer exist and are removed; the fast-path test
keeps its marked-override case.
Reserved-name validation means only a marked method override can define a
fast-pathed framework name, so the recursive prune after dynamic registration
is removed.
Comment thread reflex/istate/validation.py
@masenf
masenf merged commit 0d68f46 into reflex-dev:main Sep 17, 2026
126 checks passed
masenf pushed a commit that referenced this pull request Sep 17, 2026
#7136 landed, so the bespoke `_check_reserved_router_names` can go. It is
not #7136's validator that covers these names -- that one pops state vars
out of its reserved set, so the `rx_router_*` fields are not in it. What
rejects them is the framework's general inherited-var shadow detection: a
substate redeclaring `rx_router_session` shadows a `BaseState` var like
any other field and raises BaseVarShadowsInheritedVarError. That is a
better mechanism than a router-specific list, so the check is redundant.

Verified before removing: an annotated field, an unannotated class
attribute and a computed var are all rejected on a substate, and `router`
itself is rejected by #7136's validator.

Two cases are not covered, both general rather than router-specific, so
neither is papered over here:

- a mixin declaring an inherited var is not rejected, and neither is a
  state consuming it; the field is silently shadowed. This is not about
  the router -- a mixin can shadow any inherited base var, while a direct
  subclass doing the same is correctly rejected.
- a direct `BaseState` subclass starts its own root, so there is no
  inherited var to shadow.

`test_router_field_names_are_reserved` is narrowed to the substate path it
actually guarantees, and the breaking-change fragment now names the error
callers will really see.

The only merge conflict was the `reflex.istate.data` import list in
tests/units/test_state.py; resolved as the union of both sides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mwk1pagH3KZ884Xw55BNMh
masenf pushed a commit that referenced this pull request Sep 19, 2026
…o, probe notes, tools

FINDING-001: rx.State's new _StateMeta (#7136) makes any metaclass derived from
reflex.vars.BaseStateMeta unusable on a State subclass; reflex-enterprise 0.9.5 OIDC auth
fails to import on 0.9.12a1 and imports on 0.9.11.post1. Framework-only repro included.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeyxWsuC9hqSKq8YcEwZyY
masenf pushed a commit that referenced this pull request Sep 19, 2026
#7136 added `_StateMeta(BaseStateMeta)` and made
`class BaseState(EvenMoreBasicBaseState, metaclass=_StateMeta)`, so
`type(rx.State)` stopped being `reflex_base.vars.BaseStateMeta`. A downstream
metaclass written as `class M(BaseStateMeta)` is then a sibling of `_StateMeta`
rather than a subclass, and `class S(rx.State, metaclass=M)` fails with
`TypeError: metaclass conflict`. reflex-enterprise 0.9.5 does exactly that in
`reflex_enterprise/auth/oidc/state.py`, so every AuthPlugin / MCPPlugin /
EventHandlerAPIPlugin app dies at startup.

The conflict is raised by `__build_class__` while it resolves the most derived
metaclass, before any metaclass code runs, so nothing on `_StateMeta` can
intercept it: `type(rx.State)` has to be `BaseStateMeta` again. The
reserved-name validation therefore moves into `BaseStateMeta.__new__`, as a
hook `reflex.state` installs once `BaseState` exists, and `BaseState` goes back
to inheriting `BaseStateMeta`. The check is guarded on "a base is a BaseState",
so plain models built on the same metaclass keep their own namespace.

Fixes #7211

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeyxWsuC9hqSKq8YcEwZyY
(cherry picked from commit 6ab3edbc941c0bc3cfc2e2443a65347c6af03045)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants