Skip to content

💥 Nexus Error Serialization Changes#1973

Draft
VegetarianOrc wants to merge 18 commits intomainfrom
amazzeo/nexus-error-serialization
Draft

💥 Nexus Error Serialization Changes#1973
VegetarianOrc wants to merge 18 commits intomainfrom
amazzeo/nexus-error-serialization

Conversation

@VegetarianOrc
Copy link
Contributor

Note: This relies on an unreleased version of nexus-rpc. This cannot be merged until the version is published to NPM and this branch updated to use it.

What was changed

  • Failure converter updates (packages/common/src/converter/failure-converter.ts):
    • failureToError: Nexus HandlerError now preserves the original ProtoFailure via originalFailure and resolves error types through the HandlerErrorType enum instead of raw string casting.
    • errorToFailure: When a HandlerError carries an originalFailure, it is restored directly to a ProtoFailure without re-encoding.
    • Added nexusFailureToTemporalFailure and temporalFailureToNexusFailure private helpers.
  • Worker Nexus conversions (packages/worker/src/nexus/conversions.ts):
    • handlerErrorToProto and operationErrorToProto now return ProtoFailure directly via encodeErrorToFailure.
    • Fixed bug in gRPC status code switch statement where comma-separated case values silently matched only the last value.
  • Task completion responses (packages/worker/src/nexus/index.ts, packages/worker/src/worker.ts): Changed from error/operationError fields to failure fields in Nexus task completion protos.
  • Removed unused getInfo/getResult stubs from WorkflowRunOperationHandler.
  • Test migration: Moved the Nexus handler HTTP-based tests to workflow-caller integration tests (test-nexus-workflow-caller.ts). Updated error assertions to match the new serialization format.

Why?

Nexus error serialization now uses Temporal ProtoFailure end-to-end, letting the TypeScript SDK pass failures directly rather than converting to/from Nexus-specific proto types. Core handles backward compatibility for Nexus completions with older servers. This also fixes a JS bug where case (a, b, c): in switch statements silently evaluated only c via the comma operator, causing incorrect gRPC status code mapping.

Checklist

How was this tested:

  • Existing and updated integration tests in test-nexus-workflow-caller.ts that exercise error serialization end-to-end through workflow executions
  • Updated test-nexus-handler.ts unit tests for direct handler error conversion
  • Updated test-nexus-codec-converter-errors.ts for payload converter failure scenarios
  • Updated test-workflow-nexus-cancellation.ts for cancellation error propagation

VegetarianOrc and others added 18 commits March 10, 2026 13:56
Update the sdk-core submodule and adapt the TypeScript SDK to breaking
changes in the core API: replace RetryClient with Connection, update
error types (ClientInitError → ClientConnectError), wrap search
attributes in { indexedFields: ... }, and update codec runner and tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Chris Olszewski <chris.olszewski@temporal.io>
…fallback when a HandlerError has an original Failure that does not have the temporal failure metadata
Copy link

@Evanthx Evanthx left a comment

Choose a reason for hiding this comment

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

Not approving or disapproving as I still don't trust my knowledge of the product enough to be an approver, so just making a few comments!


private nexusFailureToTemporalFailure(failure: nexus.Failure, retryable: boolean): ProtoFailure {
if (failure.metadata?.type === 'temporal.api.failure.v1.Failure') {
return failure.details as ProtoFailure;
Copy link

Choose a reason for hiding this comment

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

Should you add a check to see if details is undefined? Not sure how that would surface back up or if it's needed, but thought it worth asking

): Promise<ProtoFailure> {
let newError: Error;
if (err.state === 'canceled') {
newError = new CancelledFailure(err.message, undefined, err.cause);
Copy link

Choose a reason for hiding this comment

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

Not an issue you introduced but I was reading some code when looking at this and CancelledFailure is defined twice in packages/client/src - once in index.ts and a second time in workflow-client.ts. So that feels like an issue waiting to happen!

case status.INVALID_ARGUMENT:
return new nexus.HandlerError('BAD_REQUEST', undefined, { cause: err });
case (status.ALREADY_EXISTS, status.FAILED_PRECONDITION, status.OUT_OF_RANGE):
case status.ALREADY_EXISTS:
Copy link

@Evanthx Evanthx Mar 19, 2026

Choose a reason for hiding this comment

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

I find logic like this (and the lines following) is usually better in the relevant class - ie, nexus.HandlerError in this case - if possible. What happens over time is that you make a change to HandlerError to add some cases, but there is no good way to track down every place using HandlerError, so not every place gets updated. But if you are in HandlerError making changes, you'll see the method right there and make the relevant changes.

Copy link
Contributor

Choose a reason for hiding this comment

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

This logic is Temporal specific, not relevant to HandlerError which is a generic Nexus error. That being said if we do one day inline the Nexus SDK into temporal then it would be a good idea to move it closer, but we haven't committed to that yet

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.

3 participants