conductor-oss / conductor-oss/csharp-sdk
DynamicFork auto-join has empty joinOn — JOIN task completes immediately without waiting for dynamic branches
- Dominant language
- C#
- Stars
- 54
- Forks
- 23
- Avg merge
- 4d 21h
- Merged PRs (30d)
- 2
Description
## Summary
`DynamicFork`'s companion `JoinTask` is constructed with zero arguments, producing empty `joinOn: []`. When added to a workflow, the JOIN task completes immediately — before any dynamic fork branches finish.
Tested against: **Conductor OSS 3.32.0-rc.9**
## Root Cause
```csharp
// Conductor/Definition/TaskType/DynamicFork.cs:32
public DynamicFork(string taskReferenceName, ...) : base(...)
{
this.Join = new JoinTask(taskReferenceName + "_join"); // no joinOn args
...
}
```
`JoinTask(string taskReferenceName, params WorkflowTask[] joinOn)` — called with zero `WorkflowTask` args → `JoinOn = new List()` on the companion join task.
When the user adds `dynamicFork.Join` to the workflow definition, the JOIN task has `joinOn: []`. The server's Join executor evaluates `joinOn.stream().allMatch(...)` — on an empty stream, `allMatch` returns `true` immediately — the JOIN transitions to `COMPLETED` without waiting for any dynamic fork branches.
## Impact
Any workflow that uses `DynamicFork.Join` to obtain the join task will have a join that completes immediately. Dynamic tasks spawned by the fork run in the background while the rest of the workflow proceeds as if the fork were done.
## Cross-SDK Confirmation
Same root cause as:
- [javascript-sdk#135](https://github.com/conductor-oss/javascript-sdk/issues/135)
- [go-sdk#264](https://github.com/conductor-oss/go-sdk/issues/264)
The server behavior (empty `joinOn` → immediate completion) was live-confirmed against Conductor OSS 3.32.0-rc.9 in those audits.
## Fix
Require callers to supply the join-on tasks, or expose it as a settable property that must be populated before use:
```csharp
public DynamicFork(string taskReferenceName, string forkTasksParameter,
string forkTasksInputsParameter, params WorkflowTask[] joinOn)
{
this.Join = new JoinTask(taskReferenceName + "_join", joinOn);
...
}
```
For dynamic forks, the tasks are determined at runtime, so the client cannot auto-infer `joinOn`. The user must explicitly pass the tasks to wait for (typically the dynamic task reference names they configure at workflow design time).
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in Conductor/Definition/TaskType/DynamicFork.cs at the constructor around line 32, then inspect the JoinTask constructor and how DynamicFork.Join is added to a workflow. Ensure the companion JOIN receives the caller-supplied tasks and verify that its serialized joinOn is populated so it waits for the dynamic branches.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100