Skip to content

Commit 1bb8bce

Browse files
mp-orkesclaude
andcommitted
fix(client): startWorkflow must not return a null workflow id
A successful response carrying no workflow id came back as a null return value, so a caller saw a successful start with no id and no way to find the execution. It now raises instead. Scoped to startWorkflow; deserialization is unchanged for every other endpoint. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 0ed0a1a commit 1bb8bce

2 files changed

Lines changed: 105 additions & 1 deletion

File tree

‎conductor-client/src/main/java/com/netflix/conductor/client/http/WorkflowClient.java‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,19 @@ public String startWorkflow(StartWorkflowRequest startWorkflowRequest) {
210210
.build();
211211

212212
ConductorClientResponse<String> resp = client.execute(request, STRING_TYPE);
213+
String workflowId = resp.getData();
214+
if (workflowId == null || workflowId.isBlank()) {
215+
// Error. Indeterminate outcome.
216+
throw new ConductorClientException(
217+
"Error when starting workflow. No workflow id was returned",
218+
resp.getStatusCode(),
219+
resp.getHeaders(),
220+
null);
221+
}
213222

214223
eventDispatcher
215224
.publish(new WorkflowStartedEvent(startWorkflowRequest.getName(), startWorkflowRequest.getVersion()));
216-
return resp.getData();
225+
return workflowId;
217226
}
218227

219228
public void checkAndUploadToExternalStorage(StartWorkflowRequest startWorkflowRequest) {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2026 Conductor Authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
* <p>
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* <p>
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*/
13+
package com.netflix.conductor.client.http;
14+
15+
import java.io.IOException;
16+
17+
import org.junit.jupiter.api.AfterEach;
18+
import org.junit.jupiter.api.BeforeEach;
19+
import org.junit.jupiter.api.Test;
20+
21+
import com.netflix.conductor.client.exception.ConductorClientException;
22+
import com.netflix.conductor.common.metadata.workflow.RerunWorkflowRequest;
23+
import com.netflix.conductor.common.metadata.workflow.StartWorkflowRequest;
24+
25+
import okhttp3.mockwebserver.MockResponse;
26+
import okhttp3.mockwebserver.MockWebServer;
27+
28+
import static org.junit.jupiter.api.Assertions.assertEquals;
29+
import static org.junit.jupiter.api.Assertions.assertNull;
30+
import static org.junit.jupiter.api.Assertions.assertThrows;
31+
import static org.junit.jupiter.api.Assertions.assertTrue;
32+
33+
/**
34+
* A successful response carrying no workflow id used to come back as a null return value, so a
35+
* caller saw a "successful" start with no id and no error. It must raise instead.
36+
*/
37+
class StartWorkflowEmptyResponseTest {
38+
39+
private MockWebServer server;
40+
private WorkflowClient workflowClient;
41+
42+
@BeforeEach
43+
void setUp() throws IOException {
44+
server = new MockWebServer();
45+
server.start();
46+
workflowClient = new WorkflowClient(new ConductorClient(server.url("/api").toString()));
47+
}
48+
49+
@AfterEach
50+
void tearDown() throws IOException {
51+
server.shutdown();
52+
Thread.interrupted();
53+
}
54+
55+
private StartWorkflowRequest request() {
56+
StartWorkflowRequest request = new StartWorkflowRequest();
57+
request.setName("greetings");
58+
request.setVersion(1);
59+
return request;
60+
}
61+
62+
@Test
63+
void emptyTwoHundredBodyRaisesInsteadOfReturningNull() {
64+
server.enqueue(new MockResponse().setResponseCode(200).setBody(""));
65+
66+
ConductorClientException e =
67+
assertThrows(
68+
ConductorClientException.class,
69+
() -> workflowClient.startWorkflow(request()));
70+
71+
assertEquals(200, e.getStatus());
72+
assertTrue(
73+
e.getMessage().contains("No workflow id was returned"),
74+
"message should say what went wrong, was: " + e.getMessage());
75+
}
76+
77+
@Test
78+
void aBodyIsStillReturned() {
79+
server.enqueue(
80+
new MockResponse()
81+
.setResponseCode(200)
82+
.setBody("3919784b-7691-11f1-a292-00163e983f22"));
83+
84+
assertEquals(
85+
"3919784b-7691-11f1-a292-00163e983f22", workflowClient.startWorkflow(request()));
86+
}
87+
88+
@Test
89+
void otherEndpointsAreUnaffected() {
90+
// The check is scoped to startWorkflow; an empty body elsewhere still deserializes to null.
91+
server.enqueue(new MockResponse().setResponseCode(200).setBody(""));
92+
93+
assertNull(workflowClient.rerunWorkflow("wf-1", new RerunWorkflowRequest()));
94+
}
95+
}

0 commit comments

Comments
 (0)