{"record":{"id":"35829481c1b35f30","repo":"invoke-ai/InvokeAI","slug":"image-move-job-job-id-has-no-items","errorCode":null,"errorMessage":"Image move job {job_id} has no items","messagePattern":"Image move job (.+?) has no items","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"invokeai/app/services/image_moves/image_moves_default.py","lineNumber":476,"sourceCode":"                continue\n            message = f\"Source image does not exist: {move.old_path}\"\n            self.create_error_move_job(move, message)\n            self._logger.error(message)\n            errors += 1\n        return remaining_moves, errors\n\n    def perform_filesystem_moves(self, job_id: int) -> None:\n        self._set_job_state(job_id, \"moving\")\n        self.complete_partial_filesystem_moves(job_id)\n        self.cleanup_empty_source_dirs(job_id)\n        self._set_job_state(job_id, \"moved\")\n\n    def complete_partial_filesystem_moves(self, job_id: int) -> None:\n        items = self._get_items(job_id, include_terminal=False)\n        if not items:\n            if self._get_items(job_id):\n                return\n            raise RuntimeError(f\"Image move job {job_id} has no items\")\n        for item in items:\n            try:\n                self._complete_partial_filesystem_move(job_id, item)\n            except Exception as e:\n                if not self._is_unrecoverable_error(e):\n                    raise\n                self._reconcile_destination_subfolder(item)\n                self.mark_item_unrecoverable(job_id, item.image_name, f\"{item.image_name}: {e}\")\n                self._logger.error(\"Image move skipped unrecoverable item %s: %s\", item.image_name, e)\n\n    def _complete_partial_filesystem_move(self, job_id: int, item: PlannedImageMove) -> None:\n        old_path = self.image_files.get_path(item.image_name, image_subfolder=item.old_subfolder)\n        new_path = self.image_files.get_path(item.image_name, image_subfolder=item.new_subfolder)\n        old_thumbnail_path = self.image_files.get_path(\n            item.image_name, thumbnail=True, image_subfolder=item.old_subfolder\n        )\n        new_thumbnail_path = self.image_files.get_path(\n            item.image_name, thumbnail=True, image_subfolder=item.new_subfolder","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/services/image_moves/image_moves_default.py#L458-L494","documentation":"complete_partial_filesystem_moves requires the job to have non-terminal items to complete. If no such items exist, it re-checks including terminal items: if any exist the job is already done and it returns silently, otherwise the job truly has no items at all and it raises RuntimeError. This catches DB inconsistency (a job row with zero item rows).","triggerScenarios":"Calling complete_partial_filesystem_moves(job_id) for a job_id that was created but whose item inserts failed/were rolled back; passing a nonexistent or wrong job_id; startup recovery encountering a corrupted job row.","commonSituations":"Manual DB edits deleting item rows; crash between INSERT of the job and its items despite the transaction; application code caching/deriving a job_id incorrectly after re-creation.","solutions":["Verify the job_id is correct and that item rows exist in image_subfolder_move_items for it","If the job is genuinely empty/corrupt, mark it 'error' (or delete it) and create a fresh job","Rely on the silent-return path: if all items are terminal, nothing needs completing","Re-run the planning flow (move_all_images) to rebuild a valid job"],"exampleFix":"// before\nservice.complete_partial_filesystem_moves(job_id)  # RuntimeError if no items\n// after\nitems = service.get_items(job_id)  # or query DB\nif not items:\n    mark_job_error(job_id)\nelse:\n    service.complete_partial_filesystem_moves(job_id)","handlingStrategy":"try-catch","validationCode":"items = get_items(job_id)  # any state\nif not items:\n    raise RuntimeError(f\"job {job_id} has no items; re-plan or mark it errored\")\nservice.complete_partial_filesystem_moves(job_id)","typeGuard":"def job_has_items(items: list[MoveItem]) -> bool:\n    return len(items) > 0","tryCatchPattern":"try:\n    service.complete_partial_filesystem_moves(job_id)\nexcept RuntimeError as e:\n    if \"has no items\" in str(e):\n        mark_job_errored(job_id)\n        job_id = replan_and_create_job()\n    else:\n        raise","preventionTips":["Never delete or hand-edit item rows in image_subfolder_move_items","Confirm job_id provenance after re-creating jobs (don't reuse stale IDs)","Rely on the silent-return semantics when all items are already terminal"],"tags":["image-move","job-state","database","runtime"],"backgroundTag":"job-state-inconsistent","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}