Skip to content

feat: surface unprocessable state deltas as fatal client errors - #6827

Open
FarhanAliRaza wants to merge 4 commits into
reflex-dev:mainfrom
FarhanAliRaza:fix/unprocessable-delta-client-error
Open

feat: surface unprocessable state deltas as fatal client errors#6827
FarhanAliRaza wants to merge 4 commits into
reflex-dev:mainfrom
FarhanAliRaza:fix/unprocessable-delta-client-error

Conversation

@FarhanAliRaza

@FarhanAliRaza FarhanAliRaza commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

When the backend sends a delta the frontend cannot process (no dispatch function registered for a substate), the app currently throws dispatch[substate] is not a function in the browser console and otherwise looks silently broken. This PR makes that failure loud, actionable, and visible in the backend terminal — where Python devs look first.

Fixes #6019. Supersedes #6128, rebuilt on top of the packages/reflex-base layout with the review findings from that PR addressed.

Frontend (reflex_base/.templates/web/utils/state.js)

  • The entire delta is validated before anything dispatches, so a bad substate can no longer partially apply an update or silently drop update.events.
  • On mismatch, the frontend logs an actionable error to the browser console and reports it to the backend via a new client_error socket event.
  • The mismatch is treated as fatal, as requested in Need a better error path when backend sends unprocessable delta #6019: a backend_state_mismatch flag stops all further event sending and drops incoming updates, so the error is reported once instead of on every interaction. A page reload (e.g. after rebuilding the frontend) resets it.
  • Unexpected errors while applying a delta are caught and reported to the backend too, instead of vanishing as unhandled promise rejections.

Backend (reflex/app.py)

  • New EventNamespace.on_client_error handler logs frontend-reported errors in the terminal with remediation steps (rebuild frontend / check api_url).
  • Hardened against abuse, since any socket can emit this event: non-dict payloads are ignored, client-supplied strings are sanitized (control chars/ANSI escapes stripped) and truncated before logging, and only sockets with a linked token can produce error-level log entries — unknown sids are logged at debug level.
  • emit_update gained debug logging of outgoing substates, guarded by console.is_debug() so the hot path doesn't pay for message construction.

Shared constants

  • SocketEvent.CLIENT_ERROR and a new ClientErrorType namespace in reflex_base.constants.event keep the error-type strings in one place, matched by the ERROR_TYPE_* constants in state.js.

Testing

  • New tests/units/test_client_error.py covers both error branches, malformed payloads (which previously raised AttributeError), unknown-sid gating, and sanitization/truncation.
  • tests/units/test_app.py and tests/units/utils/test_token_manager.py pass alongside (164 passed).

Review in cubic

When the backend sends a delta with a substate the frontend has no
dispatch function for (mismatched frontend/backend state definitions),
the frontend now:

- validates the entire delta before dispatching anything, so a bad
  substate no longer partially applies an update or silently drops
  queued events,
- logs an actionable error to the browser console,
- reports the error to the backend via a new client_error socket event
  so it shows up in the terminal where devs look first,
- treats the mismatch as fatal per reflex-dev#6019: no further events are sent
  until the frontend is rebuilt/reloaded, instead of erroring again on
  every interaction.

Unexpected errors while applying a delta are likewise reported to the
backend instead of vanishing as unhandled rejections.

The backend on_client_error handler validates the payload shape,
sanitizes and truncates client-supplied strings before logging, and
only logs at error level for sockets with a linked token. Error type
strings are shared via constants.ClientErrorType, and emit_update
gained debug logging of outgoing substates (guarded by is_debug so the
hot path is unaffected).

Fixes reflex-dev#6019
@FarhanAliRaza
FarhanAliRaza requested a review from a team as a code owner July 31, 2026 20:35
@codspeed-hq

codspeed-hq Bot commented Jul 31, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing FarhanAliRaza:fix/unprocessable-delta-client-error (ba8beb3) with main (9fcce60)

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.

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR surfaces frontend state-delta processing failures to the backend and makes dispatch mismatches fatal.

  • Validates complete deltas before applying reducer updates.
  • Adds sanitized client-error handling and bounded backend reporting.
  • Introduces shared socket-event and client-error-type constants.
  • Adds regression coverage for malformed reports, sanitization, unknown sessions, and rate limiting.

Confidence Score: 4/5

The PR is not yet safe to merge because the shared reporting budget can hide another session's sole fatal state-mismatch report.

Any socket with a client-provided token can consume the process-wide 20-report window, after which an unrelated dispatch-mismatch report is discarded even though the affected frontend enters a fatal state and does not retry it.

Files Needing Attention: reflex/app.py

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/.templates/web/utils/state.js Adds pre-dispatch delta validation, fatal mismatch handling, and backend reporting for frontend processing errors.
reflex/app.py Adds sanitized, rate-limited handling of frontend error reports through the configured frontend exception handler.
packages/reflex-base/src/reflex_base/constants/event.py Defines the shared client-error socket event and error-type constants.
tests/units/test_client_error.py Covers client-error formatting, sanitization, malformed inputs, unknown sessions, and reporting limits.

Reviews (4): Last reviewed commit: "fix: route client_error reports through ..." | Re-trigger Greptile

Comment thread reflex/app.py Outdated
Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/state.js

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cfd1341892

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread reflex/app.py

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 7 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread reflex/app.py
Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/state.js
Comment thread reflex/app.py
Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/state.js Outdated
Comment thread packages/reflex-base/src/reflex_base/constants/event.py
Comment thread reflex/app.py Outdated
Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/state.js Outdated
- Rate-limit error-level client_error logging to 5 entries per SID
  (cleared on disconnect) so a client that links an arbitrary token
  cannot flood backend logs.
- Escape rich markup in sanitized client values; unescaped closing tags
  raised MarkupError and styling tags could inject into terminal logs.
- Keep sanitized values within max_length including the truncation
  suffix.
- Clear the event queue on fatal state mismatch; callers drain the
  queue in while-loops that would otherwise spin forever.
- Await queueEvents inside the event handler try block so failures are
  reported via client_error instead of unhandled rejections; guard
  error.message for non-Error throws.
- Add news fragments for the changelog check.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 5 files (changes from recent commits).

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

Fix all with cubic | Re-trigger cubic

Comment thread reflex/app.py
Per-SID budgets reset when a new socket connects, so scripted
reconnect loops could still flood backend logs. Add a process-wide
time-window cap (20 entries per 60s) on top of the per-SID limit;
later windows log again, so long-lived sessions are not silenced
forever.
Comment thread reflex/app.py

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Fix all with cubic | Re-trigger cubic

Comment thread reflex/app.py

@masenf masenf left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if we're introducing a new client error message type on the transport, then we should wire up the existing frontend error handler to this mechanism.

Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/state.js Outdated
Comment thread reflex/app.py Outdated
- Wire on_client_error into app.frontend_exception_handler so custom
  handlers (e.g. error trackers) receive client-reported errors.
- Reword the frontend mismatch message to suggest refreshing the page
  first, per review.
- Remove the per-update substate debug log (too spammy for --loglevel
  debug).
- Warn once per window when the client_error rate limit trips so
  suppression is never silent.
@FarhanAliRaza

Copy link
Copy Markdown
Contributor Author

Wired the client_error mechanism into the existing frontend error handler in ba8beb3: on_client_error now routes reports through app.frontend_exception_handler (the same hook the ErrorBoundary flow uses), so custom handlers (e.g. error trackers) receive client-reported errors too. The default handler preserves the previous console.error behavior. Sanitization and rate limiting still apply before the handler is invoked.

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.

Need a better error path when backend sends unprocessable delta

2 participants