fix(web-shared): hydrate FatalError/RetryableError and Error subclasses in o11y#1942
Conversation
…es in o11y The web o11y reviver set was missing entries for the recently-added serialization types (FatalError, RetryableError, the built-in Error subclasses, AggregateError, DOMException), causing devalue.unflatten to throw "Unknown type X" and the UI to surface "Failed to load resource details" whenever a step or run failed with one of these error types. Adds the missing revivers to getWebRevivers() and a regression test that round-trips real values through the runtime's dehydrateStepError back through the web reviver set.
🦋 Changeset detectedLatest commit: 2d92e8c The changes in this PR will be included in the next version bump. This PR includes changesets to release 18 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
workflow with 1 step💻 Local Development
▲ Production (Vercel)
workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
workflow with 10 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
workflow with 25 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
workflow with 50 sequential data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
workflow with 10 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
workflow with 25 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
workflow with 50 concurrent data payload steps (10KB)💻 Local Development
▲ Production (Vercel)
Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
stream pipeline with 5 transform steps (1MB)💻 Local Development
▲ Production (Vercel)
10 parallel streams (1MB each)💻 Local Development
▲ Production (Vercel)
fan-out fan-in 10 streams (1MB each)💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
❌ Some benchmark jobs failed:
Check the workflow run for details. |
🧪 E2E Test Results✅ All tests passed Summary
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
✅ 📋 Other
|
There was a problem hiding this comment.
Pull request overview
This PR fixes hydration failures in the web observability UI caused by missing browser-safe revivers for newly-added serialized error types (e.g. FatalError, RetryableError, built-in Error subclasses). It updates @workflow/web-shared’s getWebRevivers() to stay in sync with the runtime reducer set and adds regression tests that round-trip through the production wire format.
Changes:
- Extend
getWebRevivers()with revivers forFatalError,RetryableError, built-inErrorsubclasses,AggregateError, and improvedDOMException/Errorcause handling. - Add
packages/web-shared/test/hydration.test.tsto round-trip errors viadehydrateStepError→hydrateDatausing the web revivers. - Add
@workflow/errorsas a devDependency for constructing realFatalError/RetryableErrorinstances in tests.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
packages/web-shared/src/lib/hydration.ts |
Adds browser-safe error-family revivers to prevent devalue.unflatten “Unknown type X” failures in the o11y UI. |
packages/web-shared/test/hydration.test.ts |
New regression tests ensuring runtime serialization and web hydration stay compatible for error types. |
packages/web-shared/package.json |
Adds @workflow/errors to devDependencies for the new test coverage. |
pnpm-lock.yaml |
Updates lockfile for the new workspace devDependency. |
.changeset/web-shared-error-family-revivers.md |
Publishes a patch changeset describing the hydration fix. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
karthikscale3
left a comment
There was a problem hiding this comment.
AI review: Clean, targeted fix. Root cause is well-explained and all 10 missing SerializableSpecial keys are now covered — cross-checked against types.ts. The old Error reviver's stack/cause omissions are genuine correctness fixes. The 11 round-trip tests drive real values through the production wire path (dehydrateStepError), which is the right approach for catching future reducer/reviver drift. LGTM pending CI.
- Pass `cause` through ErrorOptions to the subclass constructor instead of assigning afterwards, matching `getCommonRevivers` in core. This gives the resulting `cause` property the same engine-set, non-enumerable semantics as a freshly thrown Error in the consumer realm. - Guard `RetryableError.retryAfter` against missing/undefined values from older runtime payloads — without it, `new Date(undefined)` produces an Invalid Date rather than the property being absent. Add a defensive test that drives the reviver directly with a payload missing the field.
|
No backport to The user-visible bug this PR fixes ("Unknown type FatalError" / "Unknown type RetryableError" / etc. in the o11y UI) cannot occur on To override, add the |
Summary
Fixes the
Failed to load resource details: Unknown type FatalErrorbanner in the web o11y UI (and analogous failures for any other recently-added serialization type).Root cause
The web o11y UI builds its reviver set in
getWebRevivers()atpackages/web-shared/src/lib/hydration.ts, layering web-specific overrides on top ofobservabilityReviversfrom@workflow/core. The runtime serialization layer recently grew first-class reducers for:FatalError,RetryableError(Add first-class serialization forFatalErrorandRetryableError#1513, Serializerun_failed/step_failederrors through serialization pipeline #1851)Errorsubclasses (TypeError,RangeError,EvalError,ReferenceError,SyntaxError,URIError)AggregateErrorDOMException(the canonicalAbortController.abort()signal.reason)…but
getWebRevivers()was never updated in lockstep. Whendevalue.unflattenhits a tag it can't revive, it throwsUnknown type X;hydrateResourceIOswallows that throw and the UI surfaces "Failed to load resource details".The CLI doesn't hit this because it merges in the full
getCommonRevivers()from@workflow/core/serialization(seepackages/cli/src/lib/inspect/hydration.ts:229). The web app can't do the same —getCommonRevivers()uses Node'sBufferfor base64 — so it maintains its own browser-safe set.Changes
packages/web-shared/src/lib/hydration.ts:EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIErrorrevivers via amakeWebErrorSubclassReviverhelper that resolves the constructor offglobalThisso the result is a real subclass instance.AggregateErrorreviver that preserves theerrorsarray.FatalError/RetryableErrorrevivers — these aren't browser globals, so they produce plainErrors tagged with the rightname.RetryableErrorre-hydrates the wire-encoded epoch msretryAfterback into aDate.DOMExceptionreviver (works againstglobalThis.DOMExceptionwhen available, falls back toError).Errorreviver to preservecause.packages/web-shared/test/hydration.test.ts(new): round-trip tests that drive real values through the runtime'sdehydrateStepError(the production wire path) and back throughgetWebRevivers(). Includes regression coverage for the exactFatalErrorfailure shown in the screenshot, plus assertions for every error subclass andDOMException.packages/web-shared/package.json: added@workflow/errorsas a devDep so the round-trip tests can construct realFatalError/RetryableErrorinstances.Verification
pnpm --filter @workflow/web-shared testpnpm --filter @workflow/core testattribute-panel.tsxare unrelated and present onmain.