conductor-oss/conductor · error · ConflictException
Workflow is in terminal state. Status = %s
Error message
Workflow is in terminal state. Status = %s
What it means
resetCallbacksForWorkflow loads the workflow and refuses to operate if its status is already terminal (COMPLETED, FAILED, TIMED_OUT, TERMINATED, ROLLED_BACK), throwing a ConflictException. Resetting callbacks on a finished workflow is a no-op-at-best and is rejected to avoid inconsistent state.
Source
Thrown at core/src/main/java/com/netflix/conductor/core/execution/WorkflowExecutorOps.java:144
this.activeWorkerLastPollMs = properties.getActiveWorkerLastPollTimeout().toMillis();
this.workflowStatusListener = workflowStatusListener;
this.taskStatusListener = taskStatusListener;
this.executionLockService = executionLockService;
this.parametersUtils = parametersUtils;
this.idGenerator = idGenerator;
this.systemTaskRegistry = systemTaskRegistry;
this.workflowMessageQueueDAO = workflowMessageQueueDAO;
}
/**
* @param workflowId the id of the workflow for which task callbacks are to be reset
* @throws ConflictException if the workflow is in terminal state
*/
@Override
public void resetCallbacksForWorkflow(String workflowId) {
WorkflowModel workflow = executionDAOFacade.getWorkflowModel(workflowId, true);
if (workflow.getStatus().isTerminal()) {
throw new ConflictException(
"Workflow is in terminal state. Status = %s", workflow.getStatus());
}
// Get SIMPLE tasks in SCHEDULED state that have callbackAfterSeconds > 0 and
// set the
// callbackAfterSeconds to 0
workflow.getTasks().stream()
.filter(
task ->
!systemTaskRegistry.isSystemTask(task.getTaskType())
&& SCHEDULED == task.getStatus()
&& task.getCallbackAfterSeconds() > 0)
.forEach(
task -> {
if (queueDAO.resetOffsetTime(
QueueUtils.getQueueName(task), task.getTaskId())) {
task.setCallbackAfterSeconds(0);
executionDAOFacade.updateTask(task);View on GitHub (pinned to cf7c3e4a8a)
Solutions
- Check workflow.getStatus().isTerminal() before calling resetCallbacksForWorkflow.
- If the workflow is terminal and you need to re-run, use restart or rerun instead of reset.
- Make the calling automation idempotent and skip already-terminal workflows.
Example fix
// before
workflowExecutor.resetCallbacksForWorkflow(workflowId);
// after
WorkflowModel wf = executionDAOFacade.getWorkflowModel(workflowId, true);
if (!wf.getStatus().isTerminal()) {
workflowExecutor.resetCallbacksForWorkflow(workflowId);
} Defensive patterns
Strategy: validation
Validate before calling
WorkflowModel wf = executionDAOFacade.getWorkflowModel(workflowId, true);
if (wf.getStatus().isTerminal()) {
// skip reset; surface to caller or use restart/rerun
return;
}
workflowExecutor.resetCallbacksForWorkflow(workflowId); Try / catch
try {
workflowExecutor.resetCallbacksForWorkflow(workflowId);
} catch (ConflictException e) {
// workflow already terminal; use restart/rerun instead
} Prevention
- Check isTerminal() before calling resetCallbacksForWorkflow.
- Use restart or rerun to re-run a finished workflow.
- Make reset automation idempotent and skip terminal workflows.
When it happens
Trigger: Calling the reset-callbacks API (or WorkflowExecutor.resetCallbacksForWorkflow) on a workflow that has already reached a terminal status.
Common situations: Retrying a reset after the workflow finished in the meantime; automation that polls and resets without checking status; UI button enabled on a completed workflow.
Related errors
- Workflow: %s is not in terminal state, unable to restart.
- Task %s not found
- Workflow: %s is non-restartable
- ${e.message}
- Task %s failed with status: %s and reason: '%s'
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/0b054c8f29522691.
Report an issue: GitHub.