HumanSignal/label-studio · warning · TransitionValidationError

Cannot start an already completed task

Error message

Cannot start an already completed task

What it means

The example Task 'start' transition's validate_transition raises TransitionValidationError when the task's current state is already COMPLETED. Business rule: a completed task cannot be restarted via the start transition.

Source

Thrown at label_studio/fsm/transitions.py:110

class BaseTransition(BaseModel, ABC, Generic[EntityType, StateModelType]):
    """
    Abstract base class for all declarative state transitions.

    This provides the framework for implementing transitions as first-class Pydantic
    models with built-in validation, context handling, and execution logic.

    Example usage:
        class StartTaskTransition(BaseTransition[Task, TaskState]):
            assigned_user_id: int = Field(..., description="User assigned to start the task")
            estimated_duration: Optional[int] = Field(None, description="Estimated completion time in hours")

            def get_target_state(self, context: Optional[TransitionContext[Task, TaskState]]) -> str:
                return TaskStateChoices.IN_PROGRESS

            def validate_transition(self, context: TransitionContext[Task, TaskState]) -> bool:
                if context.current_state == TaskStateChoices.COMPLETED:
                    raise TransitionValidationError("Cannot start an already completed task")
                return True

            def transition(self, context: TransitionContext[Task, TaskState]) -> Dict[str, Any]:
                return {
                    "assigned_user_id": self.assigned_user_id,
                    "estimated_duration": self.estimated_duration,
                    "started_at": context.timestamp.isoformat()
                }
    """

    model_config = ConfigDict(arbitrary_types_allowed=True, validate_assignment=True, use_enum_values=True)

    def __init__(self, **data):
        super().__init__(**data)
        self.__context: Optional[TransitionContext[EntityType, StateModelType]] = None

    @property
    def context(self) -> Optional[TransitionContext[EntityType, StateModelType]]:

View on GitHub (pinned to 0b49e9b539)

Solutions

  1. Check the task's current state before dispatching the transition; skip if already COMPLETED.
  2. Catch TransitionValidationError and surface 'task already completed' to the user instead of a generic error.
  3. Guard the start transition's target state: use a restart/redo transition for completed tasks if restarting is intended.
  4. Add idempotency handling so duplicate start requests are no-ops.

Example fix

// before
execute_transition(task, 'start')
// after
if get_current_state(task) != TaskStateChoices.COMPLETED:
    execute_transition(task, 'start')
else:
    raise TransitionValidationError('Cannot start an already completed task')
Defensive patterns

Strategy: validation

Validate before calling

current = get_current_state(task)
if current == TaskStateChoices.COMPLETED:
    raise TransitionValidationError('Cannot start an already completed task')

Type guard

def is_restartable(task) -> bool:
    return get_current_state(task) != TaskStateChoices.COMPLETED

Try / catch

try:
    execute_transition(task, 'start')
except TransitionValidationError:
    notify_user('This task is already completed and cannot be started again.')

Prevention

When it happens

Trigger: Executing the start transition on a Task whose TaskState.current_state == TaskStateChoices.COMPLETED.

Common situations: Double-clicking a start/assign button in a UI so the transition fires twice; stale client state re-submitting a start after completion; retry logic replaying an old transition request.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of HumanSignal/label-studio@0b49e9b539 (2026-08-29). Data as JSON: /api/errors/ecb0f524c42edd65. Report an issue: GitHub.