kestra-io/kestra · error · IllegalArgumentException

The execution is not paused

Error message

The execution is not paused

What it means

ExecutionService.resume (the inputs-bearing overload at line 880) only works on a paused execution. It first looks for a task run in State.Type.PAUSED; if none exists it falls back to checking the execution-level state via State.isPaused(). When neither condition holds, it throws this IllegalArgumentException, because resuming a non-paused execution has no defined semantic — the execution is either already running or already terminated. The thrown exception bubbles up from resume as a checked throws Exception on the method signature.

Source

Thrown at core/src/main/java/io/kestra/core/services/ExecutionService.java:890

     *
     * @param execution the execution to resume
     * @param newState should be RUNNING or KILLING, other states may lead to undefined behavior
     * @param flow the flow of the execution
     * @param inputs the onResume inputs
     * @return the execution in the new state.
     * @throws Exception if the state of the execution cannot be updated
     */
    public Execution resume(final Execution execution, FlowInterface flow, State.Type newState, @Nullable Map<String, Object> inputs, @Nullable Pause.Resumed resumed) throws Exception {
        var pausedTaskRun = execution
            .findFirstByState(State.Type.PAUSED);

        Execution unpausedExecution;
        if (pausedTaskRun.isPresent()) {
            unpausedExecution = this.markAs(execution, flow, pausedTaskRun.get().getId(), newState, inputs, resumed);
        } else {
            // we are in a manual execution pause, not triggered by the Pause task, so we just switch the execution to the new state.
            if (!execution.getState().isPaused()) {
                throw new IllegalArgumentException("The execution is not paused");
            }
            unpausedExecution = execution.withState(newState);
        }

        this.eventPublisher.publishEvent(CrudEvent.of(execution, unpausedExecution));
        return unpausedExecution;
    }

    /**
     * Pause a running execution.
     * The execution must be running or this call will throw an IllegalArgumentException.
     *
     * @param execution the execution to resume
     * @return the execution in the new state.
     * @throws Exception if the state of the execution cannot be updated
     */
    public Execution pause(final Execution execution) throws Exception {
        if (!execution.getState().isRunning()) {

View on GitHub (pinned to 823fada927)

Solutions

  1. Before calling resume, fetch the fresh execution and verify execution.findFirstByState(State.Type.PAUSED).isPresent() || execution.getState().isPaused(); otherwise treat it as a no-op or return a 409.
  2. Make the resume endpoint idempotent at the controller/service boundary: if the execution is not paused, return the current execution rather than throwing, so duplicate UI submissions are harmless.
  3. For UI-triggered resumes, disable the Resume button unless the execution state is PAUSED, and re-fetch the execution immediately before the call.
  4. If a Pause task timed out, do not resume — inspect the execution to confirm whether it already transitioned, and surface that to the user.

Example fix

// before
executionService.resume(execution, flow, State.Type.RUNNING, inputs, resumed);

// after
boolean hasPausedTaskRun = execution.findFirstByState(State.Type.PAUSED).isPresent();
boolean isExecutionPaused = execution.getState().isPaused();
if (!hasPausedTaskRun && !isExecutionPaused) {
    return execution; // already resumed / not paused — nothing to do
}
executionService.resume(execution, flow, State.Type.RUNNING, inputs, resumed);
Defensive patterns

Strategy: validation

Validate before calling

import io.kestra.core.models.executions.Execution;
import io.kestra.core.models.flows.State;

Execution fresh = executionRepository.findById(tenantId, executionId).orElseThrow();
boolean paused = fresh.findFirstByState(State.Type.PAUSED).isPresent()
    || fresh.getState().isPaused();
if (!paused) {
    // already resumed, or never paused — no-op
    return fresh;
}
return executionService.resume(fresh, flow, State.Type.RUNNING, inputs, resumed);

Type guard

// Java guard helper — narrow before calling resume.
public static boolean isResumable(Execution execution) {
    if (execution == null) return false;
    return execution.findFirstByState(State.Type.PAUSED).isPresent()
        || execution.getState().isPaused();
}

Try / catch

try {
    executionService.resume(execution, flow, State.Type.RUNNING, inputs, resumed);
} catch (IllegalArgumentException e) {
    if ("The execution is not paused".equals(e.getMessage())) {
        // idempotent no-op: execution already moved on; return current state
        return execution;
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling resume() on an execution that has no PAUSED task run and whose State.Type is not PAUSED — i.e. the execution is RUNNING, terminated (SUCCESS/FAILED/etc.), KILLING, QUEUED, RETRYING, BREAKPOINT, or CREATED. Entry points: the resume API endpoint, the UI 'Resume' button, or a direct call after a Pause task has already auto-resumed (e.g. its timeout fired and the execution moved on).

Common situations: Double-clicking / double-submitting resume (first call unpauses, second hits a no-longer-paused execution); a Pause task configured with a timeout that has already elapsed; race between a manual resume and the scheduler/auto-resume; resuming an execution that was never paused by a Pause task and was never manually paused via the pause API.

Related errors


AI-assisted analysis of kestra-io/kestra@823fada927 (2026-08-14). Data as JSON: /api/errors/54b0de920fc714eb. Report an issue: GitHub.