kestra-io/kestra · error · IllegalArgumentException

Only QUEUED execution can be unqueued

Error message

Only QUEUED execution can be unqueued

What it means

The `ConcurrencyLimitService.unqueue()` method transitions a queued execution to a target state. The execution must currently be in the `QUEUED` state — if it is in any other state (RUNNING, SUCCESS, FAILED, etc.), an `IllegalArgumentException` is thrown. Only QUEUED executions are tracked in the `ExecutionQueuedStateStore` and can be removed/unqueued.

Source

Thrown at core/src/main/java/io/kestra/core/services/ConcurrencyLimitService.java:28

import jakarta.inject.Inject;
import jakarta.inject.Singleton;

@Singleton
public class ConcurrencyLimitService {

    private static final Set<State.Type> VALID_TARGET_STATES = EnumSet.of(State.Type.RUNNING, State.Type.CANCELLED, State.Type.FAILED);

    @Inject
    private ExecutionQueuedStateStore executionQueuedStateStore;

    /**
     * Unqueue a queued execution.
     *
     * @throws IllegalArgumentException in case the execution is not queued or is transitioned to an unsupported state.
     */
    public Execution unqueue(Execution execution, State.Type state) {
        if (execution.getState().getCurrent() != State.Type.QUEUED) {
            throw new IllegalArgumentException("Only QUEUED execution can be unqueued");
        }

        state = (state == null) ? State.Type.RUNNING : state;

        // Validate the target state, throwing an exception if the state is invalid
        if (!VALID_TARGET_STATES.contains(state)) {
            throw new IllegalArgumentException("Invalid target state: " + state + ". Valid states are: " + VALID_TARGET_STATES);
        }

        executionQueuedStateStore.remove(execution);

        return execution.withState(state);
    }
}

View on GitHub (pinned to 823fada927)

Solutions

  1. Check `execution.getState().getCurrent() == State.Type.QUEUED` before calling `unqueue()`.
  2. Handle the case gracefully — if the execution is already past QUEUED, it may have been processed by another caller.
  3. Use optimistic locking or idempotency checks to handle race conditions.

Example fix

// before
executionService.unqueue(execution, State.Type.RUNNING);
// after
if (execution.getState().getCurrent() == State.Type.QUEUED) {
    execution = executionService.unqueue(execution, State.Type.RUNNING);
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Check execution state before unqueueing
import io.kestra.core.models.flows.State;

public static boolean isUnqueueable(Execution execution) {
    return execution.getState().getCurrent() == State.Type.QUEUED;
}

if (isUnqueueable(execution)) {
    execution = concurrencyLimitService.unqueue(execution, targetState);
}

Type guard

import { State } from './types';

function isQueued(state: State.Type): boolean {
    return state === 'QUEUED';
}

Try / catch

try {
    execution = concurrencyLimitService.unqueue(execution, state);
} catch (IllegalArgumentException e) {
    if (e.getMessage().equals("Only QUEUED execution can be unqueued")) {
        // already unqueued by another caller, or state changed
        log.info("Execution {} is no longer queued, skipping unqueue", execution.getId());
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling `unqueue()` on an execution that has already been unqueued (moved to RUNNING). Calling it on an execution that was never queued (e.g., it went straight to RUNNING because no concurrency limit applied). A race condition where the execution was unqueued by another thread before this call.

Common situations: A concurrency limit was reached, an execution was queued, then manually or automatically unqueued; a second unqueue attempt fails. The execution state changed between a status check and the unqueue call.

Related errors


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