{"record":{"id":"706b280464cb3f17","repo":"invoke-ai/InvokeAI","slug":"cannot-create-image-move-job-while-another-active","errorCode":null,"errorMessage":"Cannot create image move job while another active image move job exists","messagePattern":"Cannot create image move job while another active image move job exists","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"invokeai/app/services/image_moves/image_moves_default.py","lineNumber":353,"sourceCode":"        if record_missing_errors:\n            moves, errors = self._record_missing_source_errors(moves)\n        self.preflight_moves(moves)\n        return moves, errors\n\n    def create_move_job(self, moves: Sequence[PlannedImageMove]) -> int:\n        if not moves:\n            raise ValueError(\"Cannot create an image move job with no items\")\n        with self._db.transaction() as cursor:\n            cursor.execute(\n                \"\"\"--sql\n                SELECT 1\n                FROM image_subfolder_move_jobs\n                WHERE state NOT IN ('committed', 'error')\n                LIMIT 1;\n                \"\"\"\n            )\n            if cursor.fetchone() is not None:\n                raise ValueError(\"Cannot create image move job while another active image move job exists\")\n            cursor.execute(\"INSERT INTO image_subfolder_move_jobs (state) VALUES ('planned');\")\n            job_id = cast(int, cursor.lastrowid)\n            cursor.executemany(\n                \"\"\"--sql\n                INSERT INTO image_subfolder_move_items (\n                    job_id,\n                    image_name,\n                    old_subfolder,\n                    new_subfolder,\n                    is_intermediate,\n                    old_path,\n                    new_path,\n                    old_thumbnail_path,\n                    new_thumbnail_path,\n                    state\n                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'planned');\n                \"\"\",\n                [","sourceCodeStart":335,"sourceCodeEnd":371,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/services/image_moves/image_moves_default.py#L335-L371","documentation":"Only one image subfolder move job may be active at a time. Before inserting a new job row, create_move_job queries image_subfolder_move_jobs for any row whose state is not 'committed' or 'error' and refuses if one exists, protecting filesystem/DB consistency from concurrent moves.","triggerScenarios":"Calling create_move_job (or move_all_images) while a previous job is in state 'planned' or 'running' — e.g. a prior move crashed mid-way, or two API requests/schedulers start moves concurrently.","commonSituations":"App restart while a move job was in progress (job left in non-terminal state); double-clicking a 'move' button firing two requests; scheduled task overlapping a long-running manual move.","solutions":["Let startup recovery (startup_recovery / complete_partial_filesystem_moves) finish or finalize the existing job before starting a new one","Mark the stale job as 'error' or 'committed' once its outcome is known, then retry","Poll job status and wait for state 'committed' or 'error' before scheduling another move","Serialize move requests behind a lock/queue in the calling application"],"exampleFix":"// before\njob_id = service.create_move_job(moves)\n// after\nif service.has_active_job():  # or check state via DB/API\n    raise RuntimeError(\"wait for the current image move job to finish\")\njob_id = service.create_move_job(moves)","handlingStrategy":"validation","validationCode":"active = db.query(\"SELECT 1 FROM image_subfolder_move_jobs WHERE state NOT IN ('committed','error') LIMIT 1\").fetchone()\nif active:\n    raise RuntimeError(\"another image move job is active\")\nservice.create_move_job(moves)","typeGuard":"def can_start_job(state: str | None) -> bool:\n    return state in (None, \"committed\", \"error\")","tryCatchPattern":"try:\n    job_id = service.create_move_job(moves)\nexcept ValueError as e:\n    if \"another active image move job\" in str(e):\n        wait_for_job_completion()  # poll states\n        job_id = service.create_move_job(moves)\n    else:\n        raise","preventionTips":["Serialize move job creation behind an app-level lock/queue","Always run startup_recovery on app start to finalize stale jobs","Poll job state before scheduling new moves instead of firing immediately"],"tags":["concurrency","image-move","job-state"],"backgroundTag":"concurrent-operation-conflict","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}