Skip to content

Commit 3c4a03c

Browse files
redbugzclaude
andcommitted
docs(rum): record the dead-bundle case, split blocked from throws
blocked (main.js never arrives), n=12: the agent loaded 12/12 and a Grail beacon went out 12/12, so RUM was watching; the failed URL reached the beacon 0/12. An earlier n=4 could not be told apart from sampling, but twelve consecutive unmonitored sessions is a 0.8% event under costAndTrafficControl 33. Real gap, and not closeable from our side -- anything we ship to detect it rides in the bundle that did not arrive. throws (main.js arrives and throws at module-eval), n=8: the browser reported Script error. to the page 8/8, it reached RUM 3/8, and was legible 0/8. Detectable but never diagnosable. The 3 reporting sessions are exactly the 3 with a ~4890B Classic payload against ~4465B silent -- a ~430B delta that is the error itself, and 3/8 matches the 33% sampling rate. So the error is captured every time and transmitted about a third of the time. This is the realistic code-defect case: a syntax error fails the build and never ships, but a runtime error during module-eval compiles fine. Records four mitigation options without implementing any. Only an inline boot watchdog would cover the blocked case, since being inline and same-origin its message escapes cross-origin redaction; it would need a mount signal valid for every react-scripts consumer, and would still be subject to sampling. Adds a Retrieving the harnesses section pointing at tag rum-harness-archive-2026-08-17, since the docs cite measurements whose code is no longer carried on this branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 585a355 commit 3c4a03c

2 files changed

Lines changed: 99 additions & 9 deletions

File tree

packages/react-scripts/tools/dynatrace/EARLY_ERROR_BUFFER.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ The same harness that measured the gap proves the fix. After deploying, re-run:
144144

145145
```bash
146146
cd packages/react-scripts/tools/dynatrace/beacon-harness
147+
git show rum-harness-archive-2026-08-17:packages/react-scripts/tools/dynatrace/beacon-harness/blind-window.mjs > blind-window.mjs
148+
npm install # playwright + snappyjs, not in the committed package.json
147149
node blind-window.mjs int 10 "0,100,250,400,550,700,850,1000,1500"
148150
```
149151

@@ -170,3 +172,21 @@ The sync SRI arm was retained partly as the hedge against async losing early err
170172
works, that half of its justification goes away and only the caching argument remains — which is
171173
itself contingent on Dynatrace declining the `ETag` request. See
172174
[DYNATRACE_RUM_MECHANISMS.md](./DYNATRACE_RUM_MECHANISMS.md).
175+
176+
## Retrieving the harnesses
177+
178+
The measurement scripts cited above are **not carried on this branch** — they were exploratory
179+
tooling, and keeping them here would have put throwaway code in the shipping history. They are
180+
preserved in full at tag `rum-harness-archive-2026-08-17`, which is a permanent ref and will not be pruned.
181+
182+
Pull back a single one without checking anything out:
183+
184+
```bash
185+
git show rum-harness-archive-2026-08-17:packages/react-scripts/tools/dynatrace/beacon-harness/<name>.mjs
186+
```
187+
188+
Available: `blind-window.mjs`, `chunk-failure.mjs`, `runtime-errors.mjs`, `crossorigin-check.mjs`,
189+
`dead-bundle.mjs`, `reporter-race.mjs`. The tag's annotation lists what each one settled, plus the
190+
two traps worth knowing before re-running any of them — a hardcoded chunk index that silently
191+
passes if the app ships fewer chunks, and `page.route()` disabling the HTTP cache even on a narrow
192+
pattern.

packages/react-scripts/tools/dynatrace/RUM_ERROR_PROBE_SPEC.md

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -329,17 +329,69 @@ Two notes for whoever picks this up:
329329
Before shipping it: confirm every asset host in every environment sends `ACAO`, not just
330330
`edge.fscdn.org` on int, and roll it to int alone first.
331331

332-
### Unresolved: blocking `main.js` produces nothing at all
332+
---
333+
334+
## Answered: when the app never starts
335+
336+
The two ways this happens have different answers, so
337+
[beacon-harness/dead-bundle.mjs](https://github.com/fs-webdev/create-react-app/blob/rum-harness-archive-2026-08-17/packages/react-scripts/tools/dynatrace/beacon-harness/dead-bundle.mjs) measures them separately.
338+
339+
### `blocked` — main.js never arrives
340+
341+
Result on int, **n=12**: the agent loaded in 12/12 and a Grail beacon went out in 12/12, so RUM
342+
was demonstrably watching. The failed `main.js` URL reached the beacon in **0/12**.
343+
344+
The earlier n=4 could not distinguish this from sampling; n=12 can — under
345+
`costAndTrafficControl: 33`, twelve consecutive unmonitored sessions is a 0.8% event. **This is a
346+
real gap, and it is not closeable from our side.** Anything we might ship to detect it travels in
347+
the bundle that did not arrive. Treat it as a known blind spot and detect it elsewhere: synthetic
348+
monitoring, or a drop in server-side API traffic.
349+
350+
### `throws` — main.js arrives and throws while evaluating
351+
352+
The realistic code-defect case. A syntax error fails the build and never ships, but a runtime
353+
error during module-eval compiles cleanly — a missing browser API, a bad assumption about a
354+
global, anything that works in Chrome and dies in Safari. Result on int, n=8, with the injected
355+
throw confirmed to have evaluated in 8/8:
356+
357+
| | |
358+
| --- | --- |
359+
| Browser reported `Script error.` to the page | **8/8** |
360+
| That error reached RUM | **3/8** |
361+
| ...with a legible message | **0/8** |
362+
363+
So the failure is **detectable but never diagnosable**. Two separate limits stack:
333364

334-
Mode `early` blocks the main bundle instead of a lazy chunk. Result (n=4): **no error, no failed
335-
URL, nothing** — including in the 2 of 4 runs where the agent was already live when the request
336-
failed. That is not what the chunk result predicts.
365+
**Sampling.** The 3 sessions that reported are exactly the 3 with a ~4890-byte Classic payload,
366+
against ~4465 or ~1470 for the silent ones — a consistent ~430-byte delta that is the error
367+
itself. 3/8 ≈ 37.5% matches `costAndTrafficControl: 33`. The error is captured every time and
368+
*transmitted* about a third of the time. For an outage affecting many patrons that is ample; for
369+
a defect confined to one browser version on low traffic, it may never surface.
337370

338-
Not yet explained, and the sample is too small to lean on. Candidates: the session was a
339-
`costAndTrafficControl` stub (~1 in 3 are monitored, so 0/4 is unremarkable); or a page whose
340-
bundle never loads generates too little activity for the agent to flush a full beacon — it sent
341-
only 2. Worth an n=15 run before drawing anything from it. **If it holds, it matters**: it would
342-
mean the hardest failure to detect is the one where the app does not start at all.
371+
**Opacity.** Every one is `"Script error."` with no message, file or line — the cross-origin
372+
redaction above. You learn the application is broken, not why.
373+
374+
### What could be done, if it becomes worth doing
375+
376+
Nothing here is currently implemented; recording the options while the measurements are fresh.
377+
378+
| Option | Covers `blocked` | Covers `throws` | Cost |
379+
| --- | --- | --- | --- |
380+
| Accept it || detect only | zero |
381+
| `crossOriginLoading: 'anonymous'` | no | **full message + line** | CORS risk, deferred above |
382+
| Inline boot watchdog | **yes** | yes (as "did not mount", not a line number) | needs a reliable mount signal |
383+
| Synthetic monitoring | yes | yes | outside this repo |
384+
385+
The **inline boot watchdog** is the only one that covers the `blocked` case. Because it would be
386+
inline and same-origin, its message is not redacted: a timer set in the layout that checks whether
387+
the app mounted and, if not, reports through `dtrum.reportError` — turning an ambiguous
388+
`"Script error."` (or complete silence) into an explicit "app failed to mount". It would sit
389+
naturally beside the early-error buffer in
390+
[dynatrace.ejs](../../layout/views/partials/dynatrace.ejs).
391+
392+
Two things to resolve before building it: it needs a mount signal that holds for **every**
393+
consumer of `react-scripts`, not just this app, and its report still goes through Classic, so
394+
sampling still applies — it would improve *what* you learn, not *how often*.
343395

344396
### Flaky networks: recovered failures are invisible as errors
345397

@@ -370,3 +422,21 @@ the case the plugin was added for. Raising `retryDelay` is a separate, real find
370422
**Do not block every chunk.** Blocking all 19 stops the app booting, so no `import()` rejection
371423
handler ever runs: measured 19 resource errors, **zero** `ChunkLoadError`, zero retries. Only a
372424
single-chunk block reproduces the real failure.
425+
426+
## Retrieving the harnesses
427+
428+
The measurement scripts cited above are **not carried on this branch** — they were exploratory
429+
tooling, and keeping them here would have put throwaway code in the shipping history. They are
430+
preserved in full at tag `rum-harness-archive-2026-08-17`, which is a permanent ref and will not be pruned.
431+
432+
Pull back a single one without checking anything out:
433+
434+
```bash
435+
git show rum-harness-archive-2026-08-17:packages/react-scripts/tools/dynatrace/beacon-harness/<name>.mjs
436+
```
437+
438+
Available: `blind-window.mjs`, `chunk-failure.mjs`, `runtime-errors.mjs`, `crossorigin-check.mjs`,
439+
`dead-bundle.mjs`, `reporter-race.mjs`. The tag's annotation lists what each one settled, plus the
440+
two traps worth knowing before re-running any of them — a hardcoded chunk index that silently
441+
passes if the app ships fewer chunks, and `page.route()` disabling the HTTP cache even on a narrow
442+
pattern.

0 commit comments

Comments
 (0)