conductor-oss/conductor · error · TerminateWorkflowException
%s
Error message
%s
What it means
isTaskSkipped wraps its entire body in try/catch(Exception) and rethrows any exception's message as a TerminateWorkflowException with no cause and no structured status. The %s is just the inner exception's getMessage(). This obscures the real error type and makes the failure look like a workflow-termination when it may be a data-resolution bug (e.g. getTaskByRefName failing).
Source
Thrown at core/src/main/java/com/netflix/conductor/core/execution/DeciderService.java:1041
private int applyMaxRetryDelayCap(int delaySeconds, TaskDef taskDef) {
int cap = taskDef.getMaxRetryDelaySeconds();
return (cap > 0 && delaySeconds > cap) ? cap : delaySeconds;
}
private boolean isTaskSkipped(WorkflowTask taskToSchedule, WorkflowModel workflow) {
try {
boolean isTaskSkipped = false;
if (taskToSchedule != null) {
TaskModel t = workflow.getTaskByRefName(taskToSchedule.getTaskReferenceName());
if (t == null) {
isTaskSkipped = false;
} else if (t.getStatus().equals(SKIPPED)) {
isTaskSkipped = true;
}
}
return isTaskSkipped;
} catch (Exception e) {
throw new TerminateWorkflowException(e.getMessage());
}
}
private boolean isAyncCompleteSystemTask(TaskModel task) {
return systemTaskRegistry.isSystemTask(task.getTaskType())
&& systemTaskRegistry.get(task.getTaskType()).isAsyncComplete(task);
}
public static class DeciderOutcome {
List<TaskModel> tasksToBeScheduled = new LinkedList<>();
List<TaskModel> tasksToBeUpdated = new LinkedList<>();
boolean isComplete;
TaskModel terminateTask;
private DeciderOutcome() {}
}
}View on GitHub (pinned to cf7c3e4a8a)
Solutions
- Search server logs for the full stack trace of the wrapped exception (the message alone is insufficient).
- Validate that all WorkflowTask.taskReferenceName values in the definition are unique and non-null.
- Reproduce with the same workflow definition and inspect getTaskByRefName behavior.
- Consider improving the catch to preserve the cause — file an issue, since the current swallow loses diagnosability.
Defensive patterns
Strategy: try-catch
Validate before calling
// Validate unique, non-null taskReferenceNames at definition time.
Set<String> refs = new HashSet<>();
for (WorkflowTask t : def.getTasks()) {
if (t.getTaskReferenceName() == null || !refs.add(t.getTaskReferenceName())) {
throw new IllegalArgumentException("Duplicate/null taskReferenceName in " + def.getName());
}
} Try / catch
try {
deciderService.decide(workflow);
} catch (TerminateWorkflowException e) {
// message is opaque; pull full stack trace from server logs
LOGGER.error("isTaskSkipped wrapped exception: {}", e.getMessage());
} Prevention
- Ensure all WorkflowTask.taskReferenceName values are unique and non-null.
- Pull the full server stack trace — the wrapped message alone is insufficient.
- Consider preserving the cause (file an issue) to improve diagnosability.
When it happens
Trigger: workflow.getTaskByRefName(refName) throws inside isTaskSkipped during startWorkflow's skip-loop — e.g. duplicate reference names, corrupt task data, or a null WorkflowTask reference name.
Common situations: Two WorkflowTasks share the same taskReferenceName; a workflow model with corrupted task list; a null taskReferenceName on the task to schedule.
Related errors
- Task %s failed with status: %s and reason: '%s'
- Task %s/%s exceeded total timeout of %d seconds (elapsed %d
- Workflow timed out after %d seconds. Timeout configured as %
- Task timed out after %d seconds. Timeout configured as %d se
- No tasks found to be executed
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/b67f2b676a9c4096.
Report an issue: GitHub.