{"record":{"id":"cfa47c7149ed2a6b","repo":"HumanSignal/label-studio","slug":"no-state-model-found-for-entity-meta-model-name-cfa47c","errorCode":null,"errorMessage":"No state model found for {entity._meta.model_name} when transitioning state","messagePattern":"No state model found for (.+?) when transitioning state","errorType":"exception","errorClass":"StateManagerError","httpStatus":null,"severity":"error","filePath":"label_studio/fsm/state_manager.py","lineNumber":271,"sourceCode":"                entity=task,\n                new_state='IN_PROGRESS',\n                transition_name='start_annotation',\n                user=request.user,\n                organization_id=request.user.active_organization_id,\n                context={'assignment_id': assignment.id},\n                reason='User started annotation work'\n            )\n        \"\"\"\n        if not cls._is_fsm_enabled(user=user):\n            return True  # Feature disabled, silently succeed\n\n        # Skip if FSM is temporarily disabled (e.g., during cleanup or bulk operations)\n        if CurrentContext.is_fsm_disabled():\n            return True  # FSM disabled, silently succeed\n\n        state_model = get_state_model_for_entity(entity)\n        if not state_model:\n            raise StateManagerError(f'No state model found for {entity._meta.model_name} when transitioning state')\n\n        current_state = cls.get_current_state_value(entity)\n\n        # Prevent same-state transitions - only create state records for actual state changes\n        # This avoids creating redundant data when the effective state doesn't change\n        # However, allow forced state records for audit trails (e.g., annotation updates)\n        # IMPORTANT: Also check if a state record exists in DB - if not, we must create one\n        # even if inferred state matches target state (to persist the inferred state)\n        if current_state == new_state and not force_state_record:\n            # Verify a state record actually exists in DB (not just inferred)\n            state_record_exists = state_model.objects.filter(**{entity._meta.model_name: entity}).exists()\n            if state_record_exists:\n                return True  # Skip transition - record exists and state unchanged\n            # else: No record exists (state was inferred), continue to create record\n\n        # Optimistic concurrency control using cache-based locking\n        cache_key = cls.get_cache_key(entity)\n        lock_key = f'{cache_key}:lock'","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/fsm/state_manager.py#L253-L289","documentation":"transition_state first checks whether FSM is disabled via CurrentContext.is_fsm_disabled() (silently succeeding), then resolves the entity's state model; if the registry has none, it raises StateManagerError noting the model can't be transitioned. Transitions can only be recorded through a registered state model, so transitioning an unregistered entity type is refused rather than silently ignored.","triggerScenarios":"Calling StateManager.transition_state(entity, new_state, ...) with an entity whose model is absent from the state-model registry — custom code invoking transitions directly, signal handlers on models without FSM registration, or post-save hooks firing for entity types never onboarded to the FSM.","commonSituations":"Adding a signal handler that transitions state for every save and then firing it for a non-FSM model; renaming a model so the registry key no longer matches; running custom scripts/management commands against models registered only in the enterprise edition.","solutions":["Register a state model for the entity, or route the change through a registered transition instead of raw transition_state.","Guard the call: only invoke transition_state when get_state_model_for_entity(entity) returns a model.","Audit signal handlers/bulk-update code so they filter to FSM-managed models before calling transition_state.","If it should be a silent no-op, use the CurrentContext FSM-disable mechanism (is_fsm_disabled path) around non-FSM bulk operations."],"exampleFix":"// before\n@receiver(post_save, sender=MyModel)\ndef on_save(sender, instance, **kwargs):\n    StateManager.transition_state(instance, 'updated')  # raises for unregistered model\n\n// after\n@receiver(post_save, sender=MyModel)\ndef on_save(sender, instance, **kwargs):\n    if get_state_model_for_entity(instance):\n        StateManager.transition_state(instance, 'updated')","handlingStrategy":"validation","validationCode":"from fsm.registry import get_state_model_for_entity\nfrom core.current_request import CurrentContext\n\nif CurrentContext.is_fsm_disabled() or get_state_model_for_entity(entity) is None:\n    return True  # nothing to transition; mirror the manager's silent-skip path","typeGuard":"def can_transition(entity) -> bool:\n    from fsm.registry import get_state_model_for_entity\n    return get_state_model_for_entity(entity) is not None","tryCatchPattern":"from fsm.state_manager import StateManager, StateManagerError\ntry:\n    StateManager.transition_state(entity, new_state, user=user)\nexcept StateManagerError as e:\n    if 'No state model found' in str(e):\n        logger.info('skipping transition for unregistered model %s', entity._meta.model_name)\n    else:\n        raise","preventionTips":["Filter signal handlers and bulk jobs to FSM-registered models only","Route state changes through registered transitions rather than raw transition_state calls","Use the FSM-disable context for bulk operations on mixed entity types"],"tags":["fsm","state-manager","registry","django"],"backgroundTag":"unregistered-entity-state-model","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}