Keep literal reflex.Var tag text from hanging or corrupting string vars

AP
6d ago
+226-25

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Description

A string prop containing literal <reflex.Var>...</reflex.Var> text — for example an app that documents Reflex itself, or pasted console output — is treated as an encoded global var regardless of what the tags hold. LiteralStringVar.create / Var.__post_init__ then fail in three ways (all reproduced on main @ aeea15a):

Tag contentFailure
non-numeric (<reflex.Var>state</reflex.Var>)create spins forever: the while loop never advances value on a non-numeric match, so search finds the same match again (stack dump pinned at the while, 15 s faulthandler timeout)
numeric but unregistered (<reflex.Var>1234567890</reflex.Var>)KeyError from _global_vars[int(serialized_data)]
empty (<reflex.Var></reflex.Var>)IndexError from serialized_data[0]

The decode path (_decode_var_immutable, called from every Var.__post_init__) crashes identically on empty/unknown tags — and it strips non-var tag text from the compiled _js_expr, silently dropping the user's words from the rendered output.

Fix

Treat tag content as an encoded var only when its hash actually resolves in _global_vars; anything else is the user's own text:

  • LiteralStringVar.create: on a non-var match, keep the matched text verbatim and continue scanning after the closing tag, so the loop always advances. Literal-only results are now constructed directly instead of being re-parsed through LiteralVar.create, which would have recursed infinitely on preserved text.
  • _decode_var_immutable: same registry guard, scans by position, and leaves non-var tag text in the expression instead of stripping or crashing on it.

Real encoded markers behave exactly as before — the registry hit short-circuits to the existing code path.

Tests

New tests/units/reflex_base/vars/test_sequence.py (5 tests). On main: test_marker_like_text_is_preserved_verbatim and test_real_var_and_marker_like_text_interpolate_together hang (killed by an external 60 s timeout, faulthandler dump at sequence.py:1528/:1533), and test_unknown_marker_hash_is_preserved_verbatim / test_empty_marker_text_is_preserved_verbatim / test_var_data_decode_tolerates_marker_like_text raise KeyError / IndexError. With this PR all 5 pass.

  • uv run pytest tests/units/reflex_base/vars — 193 passed
  • uv run pytest tests/units/test_var.py — 252 passed
  • Full tests/units: 14246 passed, 11 failed — all 11 are pre-existing Windows symlink/WinError failures; running the same files on clean main (stashed) fails identically, so they are unrelated to this change.
  • ruff check / ruff format --check clean; pyright clean on the changed modules.

Validation

  • Value and compiled JS both keep the literal text: LiteralStringVar.create('use <reflex.Var>state</reflex.Var> here') renders "use <reflex.Var>state</reflex.Var> here" unchanged.
  • Mixed f-strings still interpolate: f"{var} wrote <reflex.Var>fear</reflex.Var> down" decodes the real var (state hooks intact) and keeps the tag text as literal content.

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

After these steps, you're ready to open a pull request.

a. Descriptive title above.
b. Description above.
c. No linked issue — self-found via code inspection.