{"record":{"id":"afee0c5e7b262a0d","repo":"HumanSignal/label-studio","slug":"storage-status-self-status-must-be-queued-to-m","errorCode":null,"errorMessage":"Storage status ({self.status}) must be QUEUED to move it IN_PROGRESS","messagePattern":"Storage status \\((.+?)\\) must be QUEUED to move it IN_PROGRESS","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"label_studio/io_storages/base_models.py","lineNumber":128,"sourceCode":"\n            if locked_storage.status in [self.Status.QUEUED, self.Status.IN_PROGRESS]:\n                logger.error(\n                    f'Storage {locked_storage} (id={locked_storage.id}) is already in status '\n                    f'\"{locked_storage.status}\". Cannot set to QUEUED. '\n                    f'Last sync job: {locked_storage.last_sync_job}, '\n                    f'Meta: {locked_storage.meta}'\n                )\n                return False\n\n            locked_storage._update_queued_status()\n\n            self.refresh_from_db()\n            return True\n\n    def info_set_in_progress(self):\n        # only QUEUED => IN_PROGRESS transition is possible, because in QUEUED we reset states\n        if self.status != self.Status.QUEUED:\n            raise ValueError(f'Storage status ({self.status}) must be QUEUED to move it IN_PROGRESS')\n        self.status = self.Status.IN_PROGRESS\n\n        dt = timezone.now()\n        self.meta['time_in_progress'] = str(dt)\n        # at the very beginning it's the same as in progress time\n        self.meta['time_last_ping'] = str(dt)\n        self.save(update_fields=['status', 'meta'])\n\n    @property\n    def time_in_progress(self):\n        if 'time_failure' not in self.meta:\n            return datetime.fromisoformat(self.meta['time_in_progress'])\n        else:\n            return datetime.fromisoformat(self.meta['time_failure'])\n\n    def info_set_completed(self, last_sync_count, **kwargs):\n        self.status = self.Status.COMPLETED\n        self.last_sync = timezone.now()","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/HumanSignal/label-studio/blob/0b49e9b53917880baf1dd85d574fe5541a9aafb2/label_studio/io_storages/base_models.py#L110-L146","documentation":"ImportStorage synchronizations follow a strict state machine: a scan may only start from QUEUED. info_set_in_progress raises this ValueError if the storage's current status is anything other than QUEUED (e.g. already IN_PROGRESS, FAILED, or COMPLETED), preventing concurrent or invalid transitions from clobbering state-reset logic.","triggerScenarios":"Triggering a sync while another sync is already IN_PROGRESS; calling info_set_in_progress twice without the status being reset to QUEUED; invoking _scan_and_create_links or save_annotations on a storage whose status is FAILED/COMPLETED after a previous run.","commonSituations":"Double-clicking the 'Sync Storage' button; a previous sync crashed leaving status stuck at IN_PROGRESS; multiple workers picking up the same storage sync job concurrently.","solutions":["Wait for the current synchronization to finish, or check the storage status in the UI/API before triggering another sync.","Reset the storage status to QUEUED (e.g. via the storage status endpoint or by re-saving the storage) and retry.","Fix the crashed earlier sync (check logs for the underlying failure) so the status transitions back to QUEUED."],"exampleFix":"// before\nstorage.info_set_in_progress()  # may raise if status != QUEUED\n\n// after\nstorage.refresh_from_db()\nif storage.status == storage.Status.QUEUED:\n    storage.info_set_in_progress()\nelse:\n    logger.warning('Storage %s sync skipped, status=%s', storage.pk, storage.status)","handlingStrategy":"validation","validationCode":"storage.refresh_from_db()\nif storage.status != storage.Status.QUEUED:\n    logger.warning('Skipping sync: storage status is %s', storage.status)\n    return","typeGuard":"def can_start_sync(storage) -> bool:\n    return storage.status == storage.Status.QUEUED","tryCatchPattern":"try:\n    storage.info_set_in_progress()\nexcept ValueError as e:\n    logger.warning('Sync not started: %s', e)  # storage busy or in invalid state\n    return False","preventionTips":["Disable the sync button / de-duplicate requests while a sync is in progress.","Use a DB-level lock or select_for_update around sync triggering to avoid concurrent workers.","Recover stuck storages: monitor for IN_PROGRESS storages with stale time_last_ping and reset them to QUEUED."],"tags":["state-machine","storage","synchronization","concurrency"],"backgroundTag":"invalid-state-transition","analyzedSha":"0b49e9b53917880baf1dd85d574fe5541a9aafb2","analyzedAt":"2026-08-29T00:39:52.578Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}