Skip to content

fix(route): don't let a splat catchall match paths sharing its prefix#6790

Open
chuenchen309 wants to merge 2 commits into
reflex-dev:mainfrom
chuenchen309:fix/splat-catchall-prefix-match
Open

fix(route): don't let a splat catchall match paths sharing its prefix#6790
chuenchen309 wants to merge 2 commits into
reflex-dev:mainfrom
chuenchen309:fix/splat-catchall-prefix-match

Conversation

@chuenchen309

Copy link
Copy Markdown

Problem

get_route_regex compiles the splat catchall to a bare .* with no separator in front of it, while every other segment type prepends /. So posts/[[...splat]] becomes ^/posts.*/?$, which matches any path that merely starts with the literal text:

>>> from reflex.route import get_router
>>> router = get_router(["posts/[[...splat]]"])
>>> router("/posts")            # 'posts/[[...splat]]'  β€” correct
>>> router("/postsomething")    # 'posts/[[...splat]]'  β€” wrong
>>> router("/posts-archive")    # 'posts/[[...splat]]'  β€” wrong

Why the second two are wrong

Not a judgement call β€” the frontend disagrees with it. _embed_manifest_path_for (packages/reflex-base/src/reflex_base/plugins/embed.py:42-73) maps [[...splat]] to React Router's *, so the same route compiles to posts/*, which matches /posts and its descendants but not /postsomething. The Python router and the router that actually renders the page were answering differently for the same path.

Impact

app.router feeds get_load_events (app.py:1038), called per page load from state.py:2467, and app.py:1804 (self.app.router(path) or "404"). A path resolving to the wrong route means a different page's on_load events fire than the page that renders.

Fix

.* β†’ (/.*)?, making the separator required while keeping the zero-segment case (/posts itself) matching.

I probed the other segment types against React Router's semantics before changing anything β€” single, optional, and index all already agree, so the catchall was the only divergence.

Tests

test_get_router_splat_catchall in tests/units/test_route.py, parameterized over both the matching and non-matching cases. Verified red before the fix (the two prefix cases fail, the three legitimate ones pass), green after.

uv run pytest tests/units β†’ 4340 passed. uv run ruff check . / ruff format --check clean, uv run pyright reflex tests β†’ 0 errors. (tests/units/istate/manager/test_expiration.py is flaky on main independently of this change β€” it fails ~2 runs in 5 on a pristine checkout.)

AI assistance

Written with Claude Code. I verified the repro, the React Router mapping, and the pre-existing flake myself rather than taking them on the model's word, and I'm responsible for the contents.

The catchall segment compiled to a bare `.*` with no separator, so
`posts/[[...splat]]` became `^/posts.*/?$` and matched `/postsomething`.
The frontend maps the same route to React Router's `posts/*`, which
matches the route and its descendants only, so a request could resolve
to a different page's on_load events than the page actually rendered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chuenchen309
chuenchen309 requested a review from a team as a code owner July 17, 2026 01:08
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a regex mismatch in get_route_regex where [[...splat]] catchall routes compiled to .* (no separator), causing paths that merely shared the route's prefix (e.g. /postsomething) to incorrectly match posts/[[...splat]]. The Python router and the React Router frontend disagreed on these inputs, meaning the wrong page's on_load events could fire.

  • reflex/route.py: Changes the DOUBLE_CATCHALL_SEGMENT regex fragment from .* to (/.*)?, requiring a / separator while keeping the zero-segment base path (e.g. /posts itself) matching β€” aligning with React Router's posts/* semantics.
  • tests/units/test_route.py: Adds a parameterized test test_get_router_splat_catchall that covers the base path, a trailing-slash variant, a multi-segment descendant, and the two formerly-wrong prefix-only non-matching cases.

Confidence Score: 5/5

Safe to merge β€” the change is a single targeted regex fix backed by a focused, parameterized test suite that was verified red-before/green-after.

The fix is minimal (one regex fragment), correct against both the Python fullmatch call and React Router's posts/* semantics, and the added tests exercise all the important cases including the two paths that previously matched incorrectly. No other call sites are affected and no pre-existing functionality is disturbed.

No files require special attention.

Important Files Changed

Filename Overview
reflex/route.py Single-line fix in get_route_regex: .* β†’ (/.*)? for DOUBLE_CATCHALL_SEGMENT, correctly requiring a / separator and making the capture optional for the base path; comment added explains the React Router alignment.
tests/units/test_route.py Adds parameterized test_get_router_splat_catchall covering the base path, trailing-slash variant, a multi-segment descendant, and the two prefix-only non-matching cases that exposed the bug.
news/6790.bugfix.md Changelog entry accurately describes the bug and its impact on on_load event routing.

Reviews (1): Last reviewed commit: "Add changelog fragment for #6790" | Re-trigger Greptile

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.

1 participant