Repository navigation
feat: log what a React Router request throws without its text, and read BOUNDA_ERROR_TEXT - #259
Merged
Merged
Conversation
benatespina
force-pushed
the
feat/error-text-handle-error
branch
2 times, most recently
from
November 9, 2026 22:27
a0962f9 to
49d2486
Compare
…ad BOUNDA_ERROR_TEXT React Router's default handleError logs the message and the stack of whatever a loader or an action rethrows, in production too, and a message may quote personal data. `handleError` from `createBounda` and `@bounda-dev/react-router/app` logs it under `runtime.telemetry.errorText`, and the React Router templates and the onboarding example wire it in `app/entry.server.tsx`. On Cloudflare the configuration is the same bundle in every environment, so `BOUNDA_ERROR_TEXT` in the environment overrides `errorText`: `.dev.vars` turns the text on under `wrangler dev` and `vite dev` for the Durable Object and the Worker alike. `boot()` and the CLI read it too.
…_TEXT The Worker's 500 path threw on a BOUNDA_ERROR_TEXT it does not know instead of answering; it now follows the configuration and leaves the refusal to the Durable Object. Node's handleError reads the variable before an app has booted, as boot() would, and no longer waits for a boot in flight. Both Cloudflare paths share one handleError setup, and HandleBoundaErrorFunction is exported.
Four hosts each spelled the same precedence, a host's override, then BOUNDA_ERROR_TEXT, then the configuration, then the host's default, and one drifting would reopen the leak. resolveErrorText and withErrorText in core hold it; boot(), the CLI, the Durable Object, the Worker and both handleErrors use them, with the dev server's "include" passed as a fallback. handleError now resolves its rule synchronously: under vite dev on Cloudflare it awaited an import of the configuration that the Worker cancelled once the response was sent, so nothing was logged. The Cloudflare host reads the configuration a request has already imported.
benatespina
force-pushed
the
feat/error-text-handle-error
branch
from
November 9, 2026 23:02
49d2486 to
c5f24b6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
handleErrorlogs the message and the stack of whatever a loader or an action rethrows, in production too; and on Cloudflare the dev server's"include"never reached the app, which lives in its Durable Object under a configuration bundled the same for every environment.@bounda-dev/react-routerreturns ahandleErrorthat followsruntime.telemetry.errorTextthrougherrorFields; the React Router templates andexamples/onboardingwire it inapp/entry.server.tsx.BOUNDA_ERROR_TEXToverrideserrorTextfrom the environment, so.dev.varsturns the text on in development on Cloudflare while a deployment keeps it out.Changes
handleErrorfromcreateBounda(Node and Cloudflare) and@bounda-dev/react-router/app(HandleBoundaErrorFunction, exported). It logs one line,request failed, with the method and the error's class andcode; the message, the stack and the request's path only with"include". It skips aborted requests and unwraps what React Router throws itself (no route matching). Node follows the booted app's configuration, and before any bootBOUNDA_ERROR_TEXT, thencreateBounda({ errorText })(the dev server passes"include"); it never waits for a boot in flight; loggercreateConsoleLogger()by default. Cloudflare followsBOUNDA_ERROR_TEXTin the Worker'senv, then the configuration a request has already imported;workersLoggerby default. The rule is resolved synchronously: undervite deva Worker cancels an import still pending once its response is sent, so awaiting one logged nothing. Thebounda()Vite plugin warns whenapp/entry.server.*is missing or exports nohandleError.resolveErrorText(a host's override, thenBOUNDA_ERROR_TEXT, then the configuration, then the host's own default, then"omit"), andwithErrorText, the configuration with it applied;ERROR_TEXT_VARIABLEnames the variable.boot(), the CLI, the Durable Object, the Worker and bothhandleErrors resolve through it; the dev server's"include"is passed as the fallback, not copied. A value other than"omit"or"include"throwsConfigurationErrorwhere the app starts, and is passed over (ignoreInvalidEnv) where an app's failure is logged.--error-text, for printed failures and the app's logs,bounda migrateincluded; a bad value is reported like a project that cannot load.bounda status(feat: share a lease among the background jobs, start them over after a restore, and report their status #260) opens the text of the subscribers' failures under the resolved rule,--error-textorBOUNDA_ERROR_TEXT=includealike, through the call the access log records.envand applies it to the app; the Worker's 500 log follows it, and under a value it does not know still answers its 500 under the configuration's rule;workersLoggerexported.app/entry.server.tsxfor Node (renderToPipeableStream) and Cloudflare (renderToReadableStream), React Router 8.5's default entry for each runtime withhandleErrorexported and called from the render'sonError;BOUNDA_ERROR_TEXT=includein.dev.vars, and only a comment in.dev.vars.example, so Deploy to Cloudflare never asks for it.Tests
createRequestHandlerover a server build whose loader rethrows: the default logs the message (control); with"omit"neither the message nor the stack reaches the console or the logger; with"include"both do.create-bounda: a request no route matches makes React Router callhandleError; the dev server logs its text,react-router-servedoes not; on Cloudflarevite devandwrangler devlog it under.dev.vars, andwrangler devwithout the line does not; the no-framework Worker logs the object's refusal with its text under.dev.vars.resolveErrorText/withErrorText, and one per host:boot(), the CLI, the Durable Object built with anenvof the test's own, the Worker, the Node host and both CloudflarehandleErrors.process?.envNode always has, now gone; the object's#app?.configfor an alarm on an object whose app failed to start) or fixed.Notes
pnpm checkis green exceptastro checkindocs, which fails locally on macOS on a cleanmaintoo (@astrojs/checknot detected, then an emnapi crash); CI runs it.pnpm templates:sync): their.dev.vars.examplegains the comment.Left out
Nothing.