Skip to content

NEXUS-474: Support UpdateWorkflow as a Nexus Operation#2417

Draft
mavemuri wants to merge 3 commits into
mainfrom
mavemuri/nexus-op-updateworkflow
Draft

NEXUS-474: Support UpdateWorkflow as a Nexus Operation#2417
mavemuri wants to merge 3 commits into
mainfrom
mavemuri/nexus-op-updateworkflow

Conversation

@mavemuri

@mavemuri mavemuri commented Jun 23, 2026

Copy link
Copy Markdown

What was changed

Adds support for UpdateWorkflow as a Nexus operation

Why?

Part of effort to expose all Temporal primitives via Nexus operations

Checklist

  1. Closes

  2. How was this tested:

  • added integration tests - run as go run . integration-test -dev-server -run UpdateWorkflowOperation
  1. Any docs updates needed?
  • TBD

@CLAassistant

CLAassistant commented Jun 23, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@mavemuri mavemuri force-pushed the mavemuri/nexus-op-updateworkflow branch from 7b25ad4 to a5d2648 Compare June 23, 2026 22:53
@@ -1,6 +1,6 @@
module go.temporal.io/sdk/contrib/aws/lambdaworker

go 1.24.0

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.

I'd avoid bumping the required Go Version in this PR since this declares the min. Go Version our users need.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

version bump seems to be from here - temporalio/api-go@d1423d2 (in-turn here)- went in v1.62.13+

We could maybe fix it all the way back but not sure as there might be others already using it- is there another way? Or should we flag this for further discussion?

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.

AH okay it came from the API bump I understand, bumping to 1.25.x is fine then since it is within the min Go Version we support

Comment thread internal/client.go Outdated
setCallbacks(callbacks []*commonpb.Callback)
}

func SetLinksOnNexusOperation(opts nexusOperationOptions, links []*commonpb.Link) {

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.

Nit: GoDocs

Comment thread internal/internal_update.go Outdated

// workflow is async iff update stage is WorkflowUpdateStageAccepted-
// as WorkflowUpdateStageCompleted blocks on UpdateWorkflow itself
func (w WorkflowUpdateStage) IsAsyncUpdateWorkflow() bool {

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.

WorkflowUpdateStage is a type we expose to users, don't think we want to expose this helper to users so I would at least make it private or inline it. I would personally lean towards inlining it since it is so simple

WorkflowID: wfToken.WorkflowID,
}, options)
case operationTokenTypeUpdateWorkflow:
return &nexus.HandlerError{

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.

The default may be to fail, but we should still allow users to customize the cancellation behaviour, so here we should call o.options.CancelWorkflowUpdate and then the default value for that lambda would be to error

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Could it be confusing for users to trigger a cancel UpdateWorkflow on async? I was thinking that because there isnt a way currently to cancel an UpdateWorkflow, this also remains consistent with that - please correct/add if needed

maybe for another discussion- but could there be some compensating ops/events for such events?

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.

I was thinking that because there isnt a way currently to cancel an UpdateWorkflow

There isn't a built in way to cancel an UpdateWorkflow yes, but a user could build their own way to cancel a WorkflowUpdate

Comment thread temporalnexus/temporal_operation.go Outdated
}, options)
case operationTokenTypeUpdateWorkflow:
return &nexus.HandlerError{
Type: nexus.HandlerErrorTypeBadRequest,

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.

I think nexus.HandlerErrorTypeNotImplemented fits this situation better, what do you think?

Comment thread temporalnexus/token.go
operationTokenTypeWorkflowRun = operationTokenType(1)
operationTokenTypeReserved operationTokenType = iota
operationTokenTypeWorkflowRun
operationTokenTypeUpdateWorkflow

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

thanks! missed this doc

Comment thread temporalnexus/token.go

// updateWorkflow contains only meta - because it cannot be cancelled
type updateWorkflowOperationToken struct {
operationToken

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.

Missing Workflow ID and Update ID , the expected token format is defined here https://app.notion.com/p/temporalio/Nexus-Operation-Token-Format-3738fc56773880d483dcd02931d6f90a?source=copy_link

Comment thread test/nexus_update_op_test.go Outdated
}

func getAddOperation(isAsync bool) (nexus.Operation[updateAddInput, updateAddOutput], error) {
updateStage := client.WorkflowUpdateStageCompleted

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.

I think for now we should only support the client.WorkflowUpdateStageAccepted , client.WorkflowUpdateStageCompleted is a big footgun for users since it required the entire update to complete in under 10s

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Was thinking it should be 1:1 with primitives because there could be edge use-cases - like a best effort update that still uses the validation logic and has the provenance in caller etc so they dont use signal nexus op

Eg pickup-driver request matching service that needs to be done in 10s or it backoff/retries with a different payload - validation may not be enough as it checks some other subsystem(nexus op chaining) later on etc

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.

So I don't think we want to be 1:1 with the primitive in the Nexus Handler because the Nexus handler has certain limits that normal callers do not have. A nexus handler only has 10s to run so if a user combines client.WorkflowUpdateStageCompleted with a long running workflow update then they could get themselves into a bad state where their handler just keeps timing out over and over. From a performance perspective I am not sure there is any value of supporting client.WorkflowUpdateStageCompleted for Nexus since the operation will always wait for the operation to complete

}

// run both via "go run . integration-test -dev-server -run UpdateWorkflowOperation"
func testUpdateWorkflowOperation(t *testing.T, isAsync bool) {

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.

Please also assert the forward and backlinks are present

}

// run both via "go run . integration-test -dev-server -run UpdateWorkflowOperation"
func testUpdateWorkflowOperation(t *testing.T, isAsync bool) {

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.

I would test a few other scenarios to make sure the SDK handles these scenarios logically:

  • Test what happens if the user tried an invalid update handler (ie not registered)
  • Test what happens if the update handler has a validator that rejects the update
  • Test what happens if the update handler returns an error
  • Test what happens if the update handler is sync (ie it returns immediately and doesn't wait)
  • Test what happens if Nexus operation targets the same, runing, workflow update (same workflow ID same update ID)
  • Test what happens if a Nexus operation targets a already completed workflow update
  • Test what happens if the user targets a invalid workflow (ie workflow already completed)

Let me know if you want to talk through any of these scenarios with me or what the expected behaviour should be

if internal.IsUpdateWorkflowCompleted(handle) {
// if workflow handle is completed and it has an error => its an unretriable error
// like validation failing on the update handler
if err := handle.Get(ctx, nil); err != nil {

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.

later we do

	if err := handle.Get(ctx, &result); err != nil {
		return TemporalOperationResult[R]{}, err
	}

I would unify these two code paths, if the handle is complete we can just return the result or error

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.

We may need to move the link code though

// submitted that would have (100%) failed later on required because there isnt a
// way for handler to otherwise tell that something has failed in a non-recoverable way.
// draft-review: should there be some kind of error from handler that can inform better
func validateUpdateWorkflowNexusOperation(u client.UpdateWorkflowOptions) error {

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.

The SDK should already do this validation when you call client.UpdateWorkflow

}

// fails validation, shouldnt be retried
invalidUpdateWorkflowRun, err := tc.client.ExecuteWorkflow(ctx, client.StartWorkflowOptions{

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.

Can you break these up into different sub tests

@@ -0,0 +1,350 @@
package test_test

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.

The convention would be to make these part of IntegrationTestSuite


updateHandler := func(ctx workflow.Context, amount int) (updateAddOutput, error) {
counter += amount
_ = workflow.Sleep(ctx, time.Second)

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.

A couple this we are missing is if this sleep is removed the update handler should return immediately with the result , we should also test if an error is returned here (both immediately and after sleeping)

}}}
counterWorkflowLinks := getEventLinks(ctx, tc.client, counterWorkflowRun, eventsFilter)
callerWorkflowLinks := getEventLinks(ctx, tc.client, counterUpdateWorkflowRun, eventsFilter)
// draft-review: check why the links arent promoted on a sync request - seems server related

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.

I think you need to update the link handling code temporalnexus/link_converter.go to handle the type of links you get from sync updates. Specifically Workflow links temporalio/api@6487d66#diff-de60adc512f72d87dc1e38c0f30b41a16dede9243dfaf6209b4af9b5447fa551R260

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