{"record":{"id":"4a570c737d5cf4b9","repo":"HumanSignal/label-studio","slug":"detail-transition-validation-message","errorCode":null,"errorMessage":"detail: {transition validation message}","messagePattern":"detail: (.+?)","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"label_studio/fsm/api.py","lineNumber":192,"sourceCode":"        # Execute transition\n        StateManager = get_state_manager()\n        try:\n            state_record = StateManager.execute_transition(\n                entity=entity,\n                transition_name=transition_name,\n                transition_data=transition_data,\n                user=request.user,\n                organization_id=getattr(request.user, 'active_organization_id', None),\n            )\n        except PydanticValidationError as e:\n            # Pydantic schema validation errors from transition instantiation\n            raise ValidationError({'detail': extract_message(e)})\n        except TransitionValidationError as e:\n            # Explicit validation failure\n            logger.warning(\n                f'Transition validation failed with context: {e.context} and error: {e} for entity: {entity.id}'\n            )\n            raise ValidationError({'detail': extract_message(e)})\n        # Handle feature-flag disabled path (no state record created)\n        if state_record is None:\n            response_payload = {\n                'success': True,\n                'new_state': None,\n                'state_record': None,\n            }\n        else:\n            response_payload = {\n                'success': True,\n                'new_state': state_record.state,\n                # Pass model instance; nested serializer will handle representation\n                'state_record': state_record,\n            }\n        return Response(\n            FSMTransitionExecuteResponseSerializer(response_payload, context={'request': request}).data,\n            status=status.HTTP_200_OK,\n        )","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/fsm/api.py#L174-L210","documentation":"The transition's own validate()/guard logic raised TransitionValidationError, and FSMEntityTransitionAPI.post logs it with context and re-raises it as a DRF 400 with the extracted message. Unlike [101] this is a business-rule failure — the payload may be well-formed but the transition is not allowed for this entity's current state, user, or context (e.g., invalid state graph edge, permission-of-state rule, stale expected state).","triggerScenarios":"POSTing a transition whose class raises TransitionValidationError during execution: transition not allowed from the entity's current state, guard conditions unmet, or the entity is in a state that forbids this transition. Also occurs when execute_transition's validation hooks reject with an explicit raise.","commonSituations":"Applying the same transition twice (entity already moved); concurrent editors causing a stale current state; transitions gated on entity attributes (e.g., cannot complete an unfinished task); a feature flag or org setting that disables the transition's guard.","solutions":["Fetch the entity's current state (GET state history or state endpoint) and only submit transitions valid from that state.","Read the returned detail message — it comes from the transition's own validation and names the unmet rule.","Handle concurrent updates: re-fetch the entity and retry with a transition valid from the new state.","If the rule is wrong for your workflow, adjust the transition's validation/guard code rather than bypassing the API."],"exampleFix":"// before: 'detail: Cannot complete task: task is not in progress'\nclient.executeTransition('task', id, {'transition_name': 'complete_task'})\n\n// after: check current state first\nconst state = await client.stateHistory('task', id);\nif (state.results[0]?.state === 'in_progress') {\n  await client.executeTransition('task', id, {'transition_name': 'complete_task'});\n}","handlingStrategy":"try-catch","validationCode":"from fsm.state_manager import get_state_manager\n\nsm = get_state_manager()\ncurrent = sm.get_current_state_value(entity)\nallowed = {t.name for t in sm.get_available_transitions(entity)}\nassert transition_name in allowed, f'{transition_name} not valid from state {current}'","typeGuard":"def transition_allowed(entity, transition_name: str) -> bool:\n    from fsm.state_manager import get_state_manager\n    try:\n        return transition_name in {t.name for t in get_state_manager().get_available_transitions(entity)}\n    except Exception:\n        return False","tryCatchPattern":"from fsm.transitions import TransitionValidationError\ntry:\n    state_record = StateManager.execute_transition(entity=entity, transition_name=transition_name, ...)\nexcept TransitionValidationError as e:\n    logger.warning('transition %s rejected for %s: %s', transition_name, entity.id, e)\n    # surface e to user; optionally re-fetch state and pick a valid transition","preventionTips":["Always fetch current state / available transitions before submitting a manual transition","Make transition submission idempotent: skip if the target state is already current","Handle concurrent-edit races by re-fetching the entity and retrying with a valid transition"],"tags":["validation","fsm","transition","api"],"backgroundTag":"invalid-state-transition","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}