[world-vercel] Fix run_failed event schema validation failure in lazy ref mode#1222
[world-vercel] Fix run_failed event schema validation failure in lazy ref mode#1222TooTallNate wants to merge 1 commit intomainfrom
Conversation
The EventResultWireSchema used the strict WorkflowRunSchema (discriminated union) which requires error: StructuredErrorSchema for status 'failed'. When run_failed events use remoteRefBehavior 'lazy', the server returns the error field as a RemoteRef or undefined, causing Zod validation to fail. Switch to WorkflowRunWireBaseSchema which accepts error as string, structured object, or undefined, and apply deserializeError() to normalize the run entity before returning it to consumers. Closes #1180
🦋 Changeset detectedLatest commit: ac5db6a The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 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 |
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests▲ Vercel Production (2 failed)nextjs-turbopack (1 failed):
vite (1 failed):
🌍 Community Worlds (46 failed)mongodb (1 failed):
turso (45 failed):
Details by Category❌ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
❌ Some E2E test jobs failed:
Check the workflow run for details. |
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Next.js (Turbopack) Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
|
There was a problem hiding this comment.
Pull request overview
This PR updates the @workflow/world-vercel client to avoid Zod validation failures when creating run_failed events in remoteRefBehavior: 'lazy' mode, by loosening the wire-schema for the returned run and normalizing the wire error format before returning results.
Changes:
- Exported
WorkflowRunWireBaseSchemafromruns.tsso it can be reused elsewhere. - Updated
events.createresult validation to useWorkflowRunWireBaseSchemafor the optionalrunfield and applieddeserializeError()before returningrun. - Added a changeset to publish a patch release documenting the fix.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
packages/world-vercel/src/runs.ts |
Exports WorkflowRunWireBaseSchema for reuse by event result parsing. |
packages/world-vercel/src/events.ts |
Uses the wire-base run schema for event-create responses and normalizes run errors via deserializeError(). |
.changeset/fix-run-failed-schema.md |
Declares a patch release and documents the validation fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Uses wire format schemas for step and run to handle field name mapping | ||
| // and accept both resolved and lazy (ref-based) responses from the server. | ||
| // WorkflowRunWireBaseSchema is used instead of WorkflowRunSchema because the | ||
| // server may return error as a string (legacy) or undefined (lazy ref mode). | ||
| const EventResultWireSchema = z.object({ | ||
| event: EventSchema, | ||
| run: WorkflowRunSchema.optional(), | ||
| run: WorkflowRunWireBaseSchema.optional(), | ||
| step: StepWireSchema.optional(), | ||
| hook: HookSchema.optional(), | ||
| }); |
There was a problem hiding this comment.
EventResult is typed to return run?: WorkflowRun, but swapping to WorkflowRunWireBaseSchema means the response no longer has to satisfy the WorkflowRunSchema discriminated union (e.g., a status: 'failed' run can now pass validation without an error). That can leak invalid run shapes to callers and hide backend regressions for event types that use remoteRefBehavior: 'resolve'. Consider validating run with WorkflowRunSchema.safeParse after normalization (and dropping/transforming on failure), or using the strict schema when remoteRefBehavior is resolve and the wire-base schema only for lazy.
| return { | ||
| event: filterEventData(wireResult.event, resolveData), | ||
| run: wireResult.run, | ||
| run: wireResult.run | ||
| ? deserializeError<WorkflowRun>(wireResult.run) | ||
| : undefined, |
There was a problem hiding this comment.
deserializeError<WorkflowRun>(wireResult.run) asserts the result is a WorkflowRun, but with WorkflowRunWireBaseSchema the parsed run may still violate WorkflowRun’s invariants (notably status: 'failed' without error). Returning an object that doesn’t conform to the discriminated union can break consumers that assume run.error exists for failed runs. Suggestion: after deserializing, WorkflowRunSchema.safeParse and return undefined (or synthesize an error from eventData) if it doesn’t validate.
There was a problem hiding this comment.
What do you think about this?
| return { | ||
| event: filterEventData(wireResult.event, resolveData), | ||
| run: wireResult.run, | ||
| run: wireResult.run | ||
| ? deserializeError<WorkflowRun>(wireResult.run) | ||
| : undefined, |
There was a problem hiding this comment.
What do you think about this?
Summary
EventResultWireSchemawas using the strictWorkflowRunSchema(discriminated union) to validate therunentity in the server response. Forstatus: 'failed', this requireserror: StructuredErrorSchema— butrun_failedevents useremoteRefBehavior: 'lazy', so the server returns the error field as aRemoteRefdescriptor orundefined, causing Zod validation to fail.WorkflowRunWireBaseSchemawhich accepts error as a string (legacy), structured object, orundefined(lazy ref mode).deserializeError()to normalize the wire-format run entity before returning it to consumers, matching the pattern used in all other run-returning functions inruns.ts.