From 7956d457d01a8e2546d1e07900125a17beac7797 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 10 Jun 2026 12:53:28 -0500 Subject: [PATCH 1/5] Ensuring that GetWorkflowStateAsync always returns a `WorkflowState` so the `WorkflowState.Exists` properly reflects the presence of the value (or not). Signed-off-by: Whit Waldo --- src/Dapr.Workflow/DaprWorkflowClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dapr.Workflow/DaprWorkflowClient.cs b/src/Dapr.Workflow/DaprWorkflowClient.cs index c594b1aca..d0156320f 100644 --- a/src/Dapr.Workflow/DaprWorkflowClient.cs +++ b/src/Dapr.Workflow/DaprWorkflowClient.cs @@ -138,12 +138,12 @@ public Task ScheduleNewWorkflowAsync( try { var metadata = await _innerClient.GetWorkflowMetadataAsync(instanceId, getInputsAndOutputs, cancellation); - return metadata is null ? null : new WorkflowState(metadata); + return new WorkflowState(metadata); } catch (RpcException) { // If the state doesn't exist, we typically get an `RpcException`, so this wraps this and just returns null - return null; + return new WorkflowState(null); } } From 31b20ecbc673b5086a3dfa6ff46d340c52cc1dd4 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 10 Jun 2026 13:31:27 -0500 Subject: [PATCH 2/5] Removing errors when the workflow instance cannot be found unless there's an unexpected response from the runtime Signed-off-by: Whit Waldo --- src/Dapr.Workflow/Client/WorkflowGrpcClient.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Dapr.Workflow/Client/WorkflowGrpcClient.cs b/src/Dapr.Workflow/Client/WorkflowGrpcClient.cs index 93ee2f207..92a1a25ea 100644 --- a/src/Dapr.Workflow/Client/WorkflowGrpcClient.cs +++ b/src/Dapr.Workflow/Client/WorkflowGrpcClient.cs @@ -73,17 +73,26 @@ public override async Task ScheduleNewWorkflowAsync(string workflowName, var grpcCallOptions = CreateCallOptions(cancellationToken); var response = await grpcClient.GetInstanceAsync(request, grpcCallOptions); - if (!response.Exists) + if (response is null) { logger.LogGetWorkflowMetadataInstanceNotFound(instanceId); return null; } + if (!response.Exists) + { + return null; + } + return ProtoConverters.ToWorkflowMetadata(response.WorkflowState, serializer); } catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound) { - logger.LogGetWorkflowMetadataInstanceNotFound(ex, instanceId); + return null; + } + catch (Exception ex) + { + logger.LogError(ex, "Error getting workflow metadata for instance '{InstanceId}'", instanceId); return null; } } From 6c020741b35d02fd320f3550e785aa81a8939294 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Wed, 10 Jun 2026 13:37:15 -0500 Subject: [PATCH 3/5] Fixing unit tests Signed-off-by: Whit Waldo --- test/Dapr.Workflow.Test/DaprWorkflowClientTests.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/Dapr.Workflow.Test/DaprWorkflowClientTests.cs b/test/Dapr.Workflow.Test/DaprWorkflowClientTests.cs index e60667fc3..b483a2ce2 100644 --- a/test/Dapr.Workflow.Test/DaprWorkflowClientTests.cs +++ b/test/Dapr.Workflow.Test/DaprWorkflowClientTests.cs @@ -85,20 +85,21 @@ public async Task GetWorkflowStateAsync_ShouldThrowArgumentException_WhenInstanc } [Fact] - public async Task GetWorkflowStateAsync_ShouldReturnNull_WhenInnerReturnsNullMetadata() + public async Task GetWorkflowStateAsync_ShouldReflectNotExists_WhenInnerReturnsNullMetadata() { var inner = new CapturingWorkflowClient { GetWorkflowMetadataResult = null }; var client = new DaprWorkflowClient(inner); var state = await client.GetWorkflowStateAsync("missing", cancellation: TestContext.Current.CancellationToken); - Assert.Null(state); + Assert.NotNull(state); + Assert.False(state.Exists); Assert.Equal("missing", inner.LastGetMetadataInstanceId); Assert.True(inner.LastGetMetadataGetInputsAndOutputs); } [Fact] - public async Task GetWorkflowStateAsync_ShouldReturnNull_WhenInnerThrowsRpcException() + public async Task GetWorkflowStateAsync_ShouldReflectNotExists_WhenInnerThrowsRpcException() { var inner = new CapturingWorkflowClient { @@ -108,7 +109,8 @@ public async Task GetWorkflowStateAsync_ShouldReturnNull_WhenInnerThrowsRpcExcep var state = await client.GetWorkflowStateAsync("i", cancellation: TestContext.Current.CancellationToken); - Assert.Null(state); + Assert.NotNull(state); + Assert.False(state.Exists); } [Fact] From 26882c2832e02269732458ddfc3b1417e36c4705 Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Sat, 13 Jun 2026 03:53:31 -0500 Subject: [PATCH 4/5] Making GetWorkflowStateAsync return a non-nullable value as it should always populate now Signed-off-by: Whit Waldo --- src/Dapr.Workflow/DaprWorkflowClient.cs | 2 +- src/Dapr.Workflow/IDaprWorkflowClient.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dapr.Workflow/DaprWorkflowClient.cs b/src/Dapr.Workflow/DaprWorkflowClient.cs index d0156320f..5b58f81eb 100644 --- a/src/Dapr.Workflow/DaprWorkflowClient.cs +++ b/src/Dapr.Workflow/DaprWorkflowClient.cs @@ -128,7 +128,7 @@ public Task ScheduleNewWorkflowAsync( /// exist or an error occurs retrieving the metadata. /// This method never throws. /// - public async Task GetWorkflowStateAsync( + public async Task GetWorkflowStateAsync( string instanceId, bool getInputsAndOutputs = true, CancellationToken cancellation = default) diff --git a/src/Dapr.Workflow/IDaprWorkflowClient.cs b/src/Dapr.Workflow/IDaprWorkflowClient.cs index 7a0869f2a..e817e7d86 100644 --- a/src/Dapr.Workflow/IDaprWorkflowClient.cs +++ b/src/Dapr.Workflow/IDaprWorkflowClient.cs @@ -91,7 +91,7 @@ Task ScheduleNewWorkflowAsync( /// A object, or null if the workflow instance does not exist. /// /// Thrown if is null or empty. - Task GetWorkflowStateAsync( + Task GetWorkflowStateAsync( string instanceId, bool getInputsAndOutputs = true, CancellationToken cancellation = default); From 2dd0992c58d3d807bf85627b541b629f465df65d Mon Sep 17 00:00:00 2001 From: Whit Waldo Date: Sat, 13 Jun 2026 03:58:05 -0500 Subject: [PATCH 5/5] Fixed failing purge integration tests to address changed behavior Signed-off-by: Whit Waldo --- test/Dapr.IntegrationTest.Workflow/PurgeTests.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/Dapr.IntegrationTest.Workflow/PurgeTests.cs b/test/Dapr.IntegrationTest.Workflow/PurgeTests.cs index 07e9f767c..403a57f06 100644 --- a/test/Dapr.IntegrationTest.Workflow/PurgeTests.cs +++ b/test/Dapr.IntegrationTest.Workflow/PurgeTests.cs @@ -66,7 +66,8 @@ public async Task ShouldPurgeCompletedWorkflowInstance() // Verify workflow state no longer exists after purge var stateAfterPurge = await daprWorkflowClient.GetWorkflowStateAsync(workflowInstanceId, cancellation: TestContext.Current.CancellationToken); - Assert.Null(stateAfterPurge); + Assert.NotNull(stateAfterPurge); + Assert.False(stateAfterPurge.Exists); } [MinimumDaprRuntimeFact("1.17")] @@ -153,15 +154,13 @@ public async Task ShouldPurgeTerminatedWorkflowInstance() Assert.True(purged); var stateAfterPurge = await daprWorkflowClient.GetWorkflowStateAsync(workflowInstanceId, cancellation: TestContext.Current.CancellationToken); - Assert.Null(stateAfterPurge); + Assert.NotNull(stateAfterPurge); + Assert.False(stateAfterPurge.Exists); } private sealed class SimpleWorkflow : Workflow { - public override Task RunAsync(WorkflowContext context, string input) - { - return Task.FromResult($"Processed: {input}"); - } + public override Task RunAsync(WorkflowContext context, string input) => Task.FromResult($"Processed: {input}"); } private sealed class LongRunningWorkflow : Workflow