conductor-oss/conductor · error · TerminateWorkflowException
Task %s failed with status: %s and reason: '%s'
Error message
Task %s failed with status: %s and reason: '%s'
What it means
During decide(), the engine collects permissive-but-non-optional tasks that ended in a terminal, non-successful state. If any exist, it joins their failure reasons and throws a TerminateWorkflowException, terminating the workflow. Despite the 'permissive' name, a permissive task only avoids termination when it is also optional; a permissive+non-optional task that fails terminally still kills the workflow.
Source
Thrown at core/src/main/java/com/netflix/conductor/core/execution/DeciderService.java:311
.values()
.stream()
.filter(
t ->
t.getStatus().isTerminal()
&& !t.getStatus().isSuccessful())
.toList();
if (!permissiveTasksTerminalNonSuccessful.isEmpty()) {
final String errMsg =
permissiveTasksTerminalNonSuccessful.stream()
.map(
t ->
String.format(
"Task %s failed with status: %s and reason: '%s'",
t.getTaskId(),
t.getStatus(),
t.getReasonForIncompletion()))
.collect(Collectors.joining(". "));
throw new TerminateWorkflowException(errMsg);
}
outcome.isComplete = true;
}
return outcome;
}
@VisibleForTesting
List<TaskModel> filterNextLoopOverTasks(
List<TaskModel> tasks, TaskModel pendingTask, WorkflowModel workflow) {
// Update the task reference name and iteration
tasks.forEach(
nextTask -> {
nextTask.setReferenceTaskName(
TaskUtils.appendIteration(
nextTask.getReferenceTaskName(), pendingTask.getIteration()));
nextTask.setIteration(pendingTask.getIteration());View on GitHub (pinned to cf7c3e4a8a)
Solutions
- If the task's failure should be tolerated, also mark the WorkflowTask optional=true.
- If the task must succeed, fix the underlying task failure (check the task's reasonForIncompletion in the message).
- Increase retryCount or fix the task implementation so it succeeds.
- Re-read the failing task's status/reason quoted in the exception to root-cause it.
Example fix
// before
{ "name": "t1", "permissive": true } // optional defaults false -> still terminates on failure
// after
{ "name": "t1", "permissive": true, "optional": true } // tolerated failure Defensive patterns
Strategy: validation
Validate before calling
// At definition time, fail fast on the permissive-without-optional foot-gun.
for (WorkflowTask t : def.getTasks()) {
if (t.isPermissive() && !t.isOptional()) {
LOGGER.warn("Task {} is permissive but not optional; failure still terminates the workflow", t.getTaskReferenceName());
}
} Try / catch
try {
deciderService.decide(workflow);
} catch (TerminateWorkflowException e) {
// inspect quoted task id/status/reason in message
LOGGER.error("Workflow terminated by permissive task: {}", e.getMessage());
} Prevention
- Mark a WorkflowTask optional=true if its failure should be tolerated.
- Understand permissive alone does NOT prevent termination.
- Audit definitions for permissive+non-optional tasks before deploy.
When it happens
Trigger: A WorkflowTask marked permissive=true and optional=false reaches a terminal status that is not successful (FAILED, TIMED_OUT, CANCELED) after exhausting its attempts.
Common situations: Misunderstanding the permissive flag as 'failure is OK' when optional is false; a task with permissive=true whose upstream dependency fails; retry exhaustion on a permissive task.
Related errors
- 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
- %s
- No tasks found to be executed
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/4eea72729b7de850.
Report an issue: GitHub.