{"record":{"id":"51760efee900c761","repo":"invoke-ai/InvokeAI","slug":"cannot-create-an-image-move-job-with-no-items","errorCode":null,"errorMessage":"Cannot create an image move job with no items","messagePattern":"Cannot create an image move job with no items","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"invokeai/app/services/image_moves/image_moves_default.py","lineNumber":342,"sourceCode":"                    old_path=self.image_files.get_path(image_name, image_subfolder=old_subfolder),\n                    new_path=self.image_files.get_path(image_name, image_subfolder=new_subfolder),\n                    old_thumbnail_path=self.image_files.get_path(\n                        image_name, thumbnail=True, image_subfolder=old_subfolder\n                    ),\n                    new_thumbnail_path=self.image_files.get_path(\n                        image_name, thumbnail=True, image_subfolder=new_subfolder\n                    ),\n                )\n            )\n        errors = 0\n        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,","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/services/image_moves/image_moves_default.py#L324-L360","documentation":"create_move_job refuses to insert an image_subfolder_move_jobs row when the planned moves list is empty. An empty job would have no items to move, so the service treats it as a programming/caller mistake rather than a silent no-op. It is raised before any DB transaction begins.","triggerScenarios":"Calling create_move_job([]) directly, or calling move_all_images/_plan_batch when _record_missing_source_errors filtered out every move (e.g. all sources already missing and counted as errors) so an empty list reaches this method.","commonSituations":"Batch scripts selecting an empty subfolder; UI invoking 'move all' on a folder with no images; after a previous run already removed/errored all candidate images so planning yields zero moves.","solutions":["Check that the moves list has at least one PlannedImageMove before calling create_move_job, or return early/raise a friendlier error in the caller","In move_all_images, skip job creation when planning returns zero moves (optionally reporting 'nothing to move')","Verify preflight/record_missing_source_errors filtering isn't unexpectedly dropping all moves"],"exampleFix":"// before\nmoves, errors = planner.plan(...)\njob_id = service.create_move_job(moves)\n// after\nmoves, errors = planner.plan(...)\nif not moves:\n    return  # or raise ValueError(f\"nothing to move ({errors} errors)\")\njob_id = service.create_move_job(moves)","handlingStrategy":"validation","validationCode":"if not moves:\n    raise ValueError(\"refusing to create an image move job with no items\")\nservice.create_move_job(moves)","typeGuard":"def has_moves(moves: Sequence[PlannedImageMove]) -> bool:\n    return len(moves) > 0","tryCatchPattern":"try:\n    job_id = service.create_move_job(moves)\nexcept ValueError as e:\n    if \"no items\" in str(e):\n        log.info(\"nothing to move; skipping job creation\")\n        job_id = None\n    else:\n        raise","preventionTips":["Early-return in callers when planning yields zero moves","Count filtered/errored moves so silent filtering can't empty the batch unnoticed","Unit-test the empty-batch path of any move orchestration code"],"tags":["validation","image-move","empty-input"],"backgroundTag":"empty-input-validation","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}