Skip to content

PLA-1585: fix flaky undici telemetry tests + harden CI matrix#1473

Merged
dbutts29 merged 7 commits into
masterfrom
darrenbutts/PLA-1585/fix-flaky-undici-telemetry-test
Jun 12, 2026
Merged

PLA-1585: fix flaky undici telemetry tests + harden CI matrix#1473
dbutts29 merged 7 commits into
masterfrom
darrenbutts/PLA-1585/fix-flaky-undici-telemetry-test

Conversation

@dbutts29

@dbutts29 dbutts29 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description of the change

Fixes the flaky with fetch network capture enabled telemetry tests and hardens the CI matrix so a single floating-Node failure can no longer mask the whole suite. Test-infra only — no SDK/src changes, zero runtime impact (undici is a devDependency used only in test/server.telemetry.test.js; the SDK instruments the global fetch and never imports undici).

Background

The two fetch-capture tests mock the network with undici MockAgent + setGlobalDispatcher but assert against Node's global built-in fetch. MockAgent only intercepts the built-in fetch when its global-dispatcher symbol matches the running Node's bundled undici. undici 6 uses Symbol.for('undici.globalDispatcher.1'); undici 7 bumped it to .2. So with the pinned npm undici@6, the mock was bypassed on any Node leg that bundles undici 7: the request escaped to the real example.com/api/users and returned 404 instead of the mocked 200 (a 404 rather than a MockNotMatchedError is the tell that it escaped the mock).

This failed only on the latest leg, but with fail-fast on, that one failure cancelled the other four legs — so every PR looked broken. Master had not run green since 2026-03-11, so the regression went unnoticed.

Telemetry test fix

Measured interception across the matrix:

Node leg Bundled undici npm undici 6 MockAgent intercepts built-in fetch?
18 / 20 / 22 / 24 6.x (symbol .1) ✅ yes
latest 7.x (symbol .2) ❌ no

The fix keeps the tests asserting against each Node version's real built-in fetch (so the matrix still exercises per-version fetch behaviour) while making MockAgent intercept on every leg:

  • The undecorated undici devDep is the forward version (^7.27.2); the legacy version is the undici-v6 alias (npm:undici@^6).
  • The test dynamic-imports one by Node major: <20undici-v6 (6), >=20undici (7). undici 7's setGlobalDispatcher writes both the .1 and .2 slots, so the mock is seen whether the running Node bundles undici 6 (.1) or 7 (.2).

Two constraints shaped this:

  1. undici 7 requires Node 20+ — it references the global File constructor (added in Node 20), so it throws ReferenceError: File is not defined at load time on Node 18, a supported target (CLAUDE.md: "Minimum Node.js: 18+"). Node 18 must stay on undici 6.
  2. The import therefore must be dynamic, so the unselected major never loads — a static forward-undici (7) import would crash at load on Node 18 even if unused.

Alternatives considered and rejected:

  • Swap globalThis.fetch = undiciFetch (route global fetch through the npm undici): works on every leg, but then all legs test the npm undici's fetch instead of each version's real built-in fetch — which defeats the matrix for fetch-compatibility tests.
  • Bump everything to undici 7: fixes latest but breaks the Node 18 leg (undici 7 can't load there).

CI matrix hardening

  • fail-fast: false — one leg's failure no longer cancels the others (the cascade that made a latest-only failure look universal).
  • continue-on-error on the floating latest leg — keep it as a forward-compat canary that doesn't block PRs.
  • timeout-minutes: 20 plus a retry/timeout wrapper around playwright install chromium, which stalls indefinitely on Node 24/latest; gate the Playwright + Web Test Runner steps to stable Node (the harness Node version is irrelevant to browser coverage).

Verification

  • npm run test:server: 149 passing locally on Node 18, 20, 22, and 24; npm run lint clean.
  • CI on cf9742d0: all five legs green, including latest — the undici-7-bundled environment that can't be reproduced locally, giving live confirmation the .2 interception path works.

Type of change

  • Bug fix (non-breaking change that fixes an issue) — the flaky telemetry tests
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Maintenance — CI matrix hardening
  • New release

Related issues

Checklists

Development

  • Lint rules pass locally
  • The code changed/added as part of this pull request has been covered with tests
  • All tests related to the changed code pass in development

Code review

  • This pull request has a descriptive title and information useful to a reviewer. There may be a screenshot or screencast attached
  • "Ready for review" label attached to the PR and reviewers assigned
  • Issue from task tracker has a link to this pull request
  • Changes have been reviewed by at least one other engineer

🤖 Generated with Claude Code

The server telemetry fetch tests mock the network with undici MockAgent
(npm undici 6.23) + setGlobalDispatcher, but assert against Node's global
fetch, which uses Node's *built-in* undici. When the built-in dispatcher
diverges from the npm package's (newest Node ships undici 7.x),
setGlobalDispatcher is a no-op for global fetch: the request reaches the
real example.com and the test sees 404 instead of the mocked 200. This
fails deterministically on the CI `latest` Node leg, and fail-fast then
cancels the other legs.

Route the global fetch through the same undici instance the mock patches
(globalThis.fetch = undiciFetch) for the duration of these tests, restoring
it afterEach. Deterministic on every Node version, and still faithfully
exercises the instrumentation since Node's built-in fetch is undici.

Verified: server tests pass (149) on Node 18/22/24 locally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jun 11, 2026

Copy link
Copy Markdown

PLA-1585

…stalls

- fail-fast: false so one leg's failure no longer cancels the others (this
  cascade is what made the telemetry failure look like all legs were broken).
- continue-on-error on the floating `latest` leg: keep it as a forward-compat
  canary (it caught the undici 7.x divergence) without letting a moving,
  non-LTS target block PRs.
- timeout-minutes: 20 + retry/timeout around `playwright install chromium`,
  which can stall indefinitely on the browser download and hung the 24/latest
  legs for 40+ min.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dbutts29 dbutts29 changed the title PLA-1585: fix flaky undici fetch telemetry tests on newest Node PLA-1585: fix flaky undici telemetry tests + harden CI matrix Jun 11, 2026
@dbutts29

Copy link
Copy Markdown
Contributor Author

Folded in CI-reliability changes (same PLA-1585 goal of making rollbar.js CI trustworthy):

  • fail-fast: false — one leg's failure no longer cancels the others (the cascade that disguised this as an all-legs breakage).
  • continue-on-error on the floating latest leg — kept as a forward-compat canary (it caught the undici 7.x divergence) but it can no longer block a PR or, with fail-fast off, cancel the supported legs.
  • timeout-minutes: 20 + retry/timeout around playwright install chromium — the chromium download stalled 40+ min on the 24/latest legs (a supported version, so not solved by touching latest alone); each attempt is now capped at 5 min with up to 3 retries.

The telemetry fix is the substantive change; these make the result observable and stop a stalled browser download from hanging the matrix.

`playwright install chromium` stalls indefinitely on Node 24/latest (a
pre-existing issue that was masked by the telemetry failure until the
server tests started passing). Browser tests launch chromium, so the
harness Node version is irrelevant to browser coverage — gate the
Playwright + Web Test Runner steps to 18/20/22 and let 24/latest run
the server + validation suites only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dbutts29 dbutts29 requested review from matux and waltjones June 11, 2026 18:01
@waltjones

waltjones commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Looking at using package aliasing to install two versions of undici, so the tests continue to use the version of fetch for each Node version in the test matrix.

https://docs.npmjs.com/cli/v8/using-npm/package-spec#aliases

dbutts29 and others added 3 commits June 12, 2026 09:30
undici MockAgent already intercepts Node's built-in fetch on every blocking
leg (18/20/22/24) — they share Symbol(undici.globalDispatcher.1). The swap
only masked a failure on the non-blocking `latest` leg, where Node bundles
undici 7.x (global-dispatcher symbol bumped to .2). With fail-fast off and
`latest` on continue-on-error, that divergence now surfaces as a forward-compat
canary instead of cascading into the other legs.

Reverting the swap restores genuine per-version built-in fetch coverage for
these fetch-compatibility tests (review feedback). Verified 149 passing on
Node 22 and 24 with the reverted test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ch on all legs

The telemetry fetch tests mock via npm undici's MockAgent but assert against
Node's built-in global fetch. undici 7's setGlobalDispatcher writes both the
legacy `.1` and the current `.2` global-dispatcher symbol slots, so the mock is
seen by built-in fetch whether the running Node bundles undici 6 (reads `.1`)
or 7 (reads `.2`) — including the `latest` leg, which was the only failure.

This keeps the tests exercising each Node version's real built-in fetch (no
globalThis.fetch swap) while turning the previously-red, non-blocking `latest`
leg green. undici is a devDependency used only in test/server.telemetry.test.js
(src/ instruments the global fetch and never imports undici), so there is zero
runtime impact. Floor is ^7.27.2 because that is the verified version whose
setGlobalDispatcher writes both symbol slots. Verified 149 server tests pass on
Node 22 and 24.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-in fetch

The straight undici 7 bump fixed the `latest` leg but broke Node 18: undici 7
requires Node 20+ (it references the global `File`), so it can't even load on
18. Node 18 is a supported target (CLAUDE.md: "Minimum Node.js: 18+"), so keep
it.

Keep undici 6 as the default and add undici@7 via the `undici-v7` npm alias.
The telemetry test picks one by Node major via dynamic import: <20 → undici 6
(symbol `.1`, the only major that runs on Node 18), >=20 → undici 7 (writes both
`.1` and `.2`, so MockAgent intercepts the built-in fetch whether Node bundles
undici 6 (reads `.1`) or 7 (reads `.2`, the `latest` leg)). Dynamic import is
required so the unselected major never loads (a static undici-7 import would
crash at load on Node 18).

devDependency-only (src/ never imports undici), zero runtime impact. Verified
149 server tests pass on Node 18, 20, 22, and 24.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread test/server.telemetry.test.js Outdated
// (statically importing undici 7 on Node 18 would crash at load time).
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
const { MockAgent, getGlobalDispatcher, setGlobalDispatcher } =
nodeMajor >= 20 ? await import('undici-v7') : await import('undici');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One small nit. Let's use the decorated name for the legacy version, and the undecorated name for the forward version. e.g. 'undici' and 'undici-v6'.

@waltjones waltjones left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One comment and then looks good.

… version

Review nit: name the forward version undecorated and the legacy version
decorated. `undici` now resolves to ^7.27.2 and `undici-v6` is the
npm:undici@^6 alias (was the reverse). Test selection follows: Node >=20
imports `undici` (7, symbol .2 — and writes .1 too), Node <20 imports
`undici-v6` (6, symbol .1, the only major that runs on Node 18).

Verified 149 server tests pass on Node 18 and 20; lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dbutts29 dbutts29 merged commit 9bc5d1f into master Jun 12, 2026
7 checks passed
@dbutts29 dbutts29 deleted the darrenbutts/PLA-1585/fix-flaky-undici-telemetry-test branch June 12, 2026 21:05
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.

2 participants