Data-driven describeRunError + expose via @workflow/core/describe-error#1839
Conversation
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests💻 Local Development (2 failed)vite-stable (2 failed):
Details by Category❌ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
✅ 📋 Other
❌ Some E2E test jobs failed:
Check the workflow run for details. |
🦋 Changeset detectedLatest commit: e6b8e31 The changes in this PR will be included in the next version bump. This PR includes changesets to release 17 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 |
There was a problem hiding this comment.
Pull request overview
Adds a data-driven error description helper to complement the existing live-Error describeError, and exposes these helpers via a new public @workflow/core/describe-error subpath for observability consumers (CLI/UI) to render consistent attribution/hints without depending on the full runtime entrypoint.
Changes:
- Introduces
describeRunError({ errorCode, errorName })and shared hint string constants inpackages/core/src/describe-error.ts. - Expands unit tests to cover the persisted-signal matrix for
describeRunError. - Exposes a new
./describe-errorexport in@workflow/coreand adds a changeset.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/core/src/describe-error.ts | Adds describeRunError and refactors shared hint strings for consistent attribution/hints. |
| packages/core/src/describe-error.test.ts | Adds test coverage for the new data-driven helper. |
| packages/core/package.json | Exposes @workflow/core/describe-error subpath export for consumers. |
| .changeset/describe-error-subpath.md | Declares a patch release documenting the new public subpath and helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| */ | ||
| export interface PersistedErrorSignal { | ||
| errorCode?: RunErrorCode; |
There was a problem hiding this comment.
PersistedErrorSignal.errorCode is typed as RunErrorCode, but persisted run_failed.eventData.errorCode is currently just string | undefined (world schema allows any string for backwards compatibility). Making this field string | undefined (and coercing/validating to known RUN_ERROR_CODES internally) would avoid forcing downstream renderers to cast and prevents returning an invalid code under the RunErrorCode type.
| */ | |
| export interface PersistedErrorSignal { | |
| errorCode?: RunErrorCode; | |
| * | |
| * Note that persisted `errorCode` is stored as a plain string for backward | |
| * compatibility and must be validated/coerced against `RUN_ERROR_CODES` | |
| * before being treated as a `RunErrorCode`. | |
| */ | |
| export interface PersistedErrorSignal { | |
| errorCode?: string; |
There was a problem hiding this comment.
Fixed in e6b8e31 — PersistedErrorSignal.errorCode is now typed as string | undefined rather than RunErrorCode | undefined, matching the actual loose shape of the stored value. describeRunError runs it through a normalizeErrorCode helper that only trusts entries in RUN_ERROR_CODES and falls back to USER_ERROR otherwise.
| @@ -0,0 +1,10 @@ | |||
| --- | |||
| '@workflow/core': patch | |||
There was a problem hiding this comment.
Changeset frontmatter uses single quotes around the package name, but other changesets in this repo use double quotes. Please update the frontmatter to match existing changeset formatting so automated tooling remains consistent.
| '@workflow/core': patch | |
| "@workflow/core": patch |
There was a problem hiding this comment.
Fixed in e6b8e31 — single-quoted frontmatter switched to double-quoted.
| * event data: the classification `errorCode` plus an optional `errorName` | ||
| * (which maps to `Error#name` on the original throw). The UI does not | ||
| * have the original `Error` instance after hydration, so attribution has | ||
| * to be derived from these primitive fields. | ||
| */ | ||
| export interface PersistedErrorSignal { | ||
| errorCode?: RunErrorCode; |
There was a problem hiding this comment.
The docstring claims errorName is persisted in run_failed / step_failed event data, but the current event schemas/runtime only persist errorCode plus an error payload (no separate errorName). As a result, consumers reading hydrated events won’t be able to provide errorName to describeRunError unless the runtime/world schema is updated to persist it, or this helper is adjusted to derive the name from the persisted error payload instead.
| * event data: the classification `errorCode` plus an optional `errorName` | |
| * (which maps to `Error#name` on the original throw). The UI does not | |
| * have the original `Error` instance after hydration, so attribution has | |
| * to be derived from these primitive fields. | |
| */ | |
| export interface PersistedErrorSignal { | |
| errorCode?: RunErrorCode; | |
| * event data: the classification `errorCode` plus the serialized `error` | |
| * payload emitted by the runtime. Some callers may also provide | |
| * `errorName` as a compatibility field, but it is not a separately | |
| * persisted primitive in the current event schema. The UI does not have | |
| * the original `Error` instance after hydration, so attribution should be | |
| * derived from `errorCode` plus the hydrated error payload when needed. | |
| */ | |
| export interface PersistedErrorSignal { | |
| errorCode?: RunErrorCode; | |
| error?: { name?: string } | null; |
There was a problem hiding this comment.
Addressed in e6b8e31 — the docstring now notes that errorName is not universally persisted today, and explains that callers that have it can pass it in to sharpen the attribution. describeRunError still returns a sensible result from errorCode alone when errorName is absent.
|
Stacked: #1840 — Phase 8 (final): |
f15790b to
d8b41c0
Compare
Observability renderers read persisted run_failed / step_failed event data,
not live Error instances. describeRunError takes { errorCode, errorName }
and returns the same { attribution, hint } shape as describeError, so the
CLI and web UI can derive user-vs-SDK framing from the event log directly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ff48e25 to
e6b8e31
Compare
|
Superseded by #1849 — consolidated friendlier-errors PR with all 8 phases + follow-up fixes (ANSI leak, non-retry semantics, shared captureStackTrace helper). |
Summary
Phase 7 foundation of the friendlier-errors stack. Adds a data-driven
describeRunErroralongside the live-ErrordescribeError, and exposes both under a public@workflow/core/describe-errorsubpath so observability renderers (CLI, web UI) can use them without taking on the entire core runtime.describeRunError({ errorCode, errorName })— derives the same{ attribution, hint, errorCode }shape asdescribeError(err), but from persistedrun_failed/step_failedevent fields. After hydration, renderers only have these primitives — not the original class instance — so attribution needs a data-driven path../describe-errorsubpath on@workflow/coreso the CLI / web packages can consume these helpers without depending on the runtime entry point.SerializationError, context-violation,WorkflowRuntimeError,StepNotRegisteredError-by-name,REPLAY_TIMEOUT,MAX_DELIVERIES_EXCEEDED, missingerrorCodefallback).Scope note: deliberately scoped to the shared helper + public surface so it can merge cleanly. The UI wiring (split SDK / user banners, humanized step names, screenshot regression case) will follow separately on top of this.
Manual test plan
This PR is mostly a foundation that other renderers will consume. Test it by importing the subpath in a scratch script — no workbench needed.
Create a scratch file at the repo root:
Run with
pnpm tsx scratch.ts.SerializationErrorby name →{ attribution: 'user', errorCode: 'USER_ERROR', hint: 'A value…serialized…' }{ attribution: 'user', hint: 'A workflow-only or step-only API…' }RUNTIME_ERRORcode →{ attribution: 'sdk', hint: 'This is an internal workflow SDK error…' }REPLAY_TIMEOUTcode →{ attribution: 'sdk', hint: 'The workflow replay took too long…' }MAX_DELIVERIES_EXCEEDEDcode →{ attribution: 'sdk', hint: 'The workflow queue exceeded its max-delivery budget…' }USER_ERROR(noerrorName) →{ attribution: 'user', errorCode: 'USER_ERROR' }with nohintfield.errorCodefalls through →{ attribution: 'user', errorCode: 'USER_ERROR' }(normalized).describeError(new SerializationError('x'))anddescribeRunError({ errorCode: 'USER_ERROR', errorName: 'SerializationError' })return the same shape and same hint string (module-level constants should be shared).@workflow/core/describe-errorandpnpm tsxruns without module-resolution errors. This verifies the new export inpackage.json/dist/layout.Unit tests
pnpm --filter @workflow/core exec vitest run src/describe-error.test.ts(14 tests)dist/describe-error.js/dist/describe-error.d.tsemitted by existing build pipeline📚 Friendlier errors stack
Multi-PR initiative inspired by @Schniz's stalled #706:
Ansirendering primitives + context-violation errorsSerializationErrorat serialization / stream / encryption boundariesdescribeError)throw new Error(...)sitesdescribeRunError+ public subpathWorkflowBuildError+ applications in@workflow/buildersfunctionNameleak, simplify docs framing, redirect stack to user codeEach PR is stacked on the previous one; merge in order.
🤖 Generated with Claude Code