{"record":{"id":"d09711da78ff163f","repo":"invoke-ai/InvokeAI","slug":"image-not-found","errorCode":null,"errorMessage":"Image not found","messagePattern":"Image not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"invokeai/app/api/routers/_access.py","lineNumber":81,"sourceCode":"    flipped back to Shared, an owner re-granting access), and dropping the reference over one\n    destroys work the user cannot get back by restoring the permission.\n\n    Nothing above can tell them apart: the ownership test rests on `images.user_id`, which is\n    gone with the row, so a deleted image reaches that same 403 as a foreign one. So the\n    distinction is made here, on the refusal path only — the happy path pays nothing for it.\n\n    A storage error propagates rather than answering either, so an unreadable database cannot\n    present as a deleted image and take the user's references down with it. `exists` is a bare\n    row probe rather than `get` for the same reason from the other side: `get` deserializes, so\n    a row written by a newer version — an enum value this one does not know — would fail exactly\n    as absence does, and a live image would be reported gone.\n\n    The cost is that an authenticated caller can now tell an absent image from one they may not\n    read. Image names are generated UUIDs, so this buys an attacker nothing they could enumerate,\n    and it is the answer admins have always received.\n    \"\"\"\n    if not ApiDependencies.invoker.services.image_records.exists(image_name):\n        raise HTTPException(status_code=404, detail=\"Image not found\")\n\n\ndef assert_image_read_access(image_name: str, current_user: CurrentUserOrDefault) -> None:\n    \"\"\"Raise 403 if the current user may not view the image.\n\n    Access is granted when ANY of these hold:\n    - The user is an admin.\n    - The user owns the image.\n    - The image sits on a shared or public board.\n    \"\"\"\n    if current_user.is_admin:\n        return\n\n    owner = ApiDependencies.invoker.services.image_records.get_user_id(image_name)\n    if owner is not None and owner == current_user.user_id:\n        return\n\n    board_id = ApiDependencies.invoker.services.board_image_records.get_board_for_image(image_name)","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/api/routers/_access.py#L63-L99","documentation":"`_assert_image_record_exists` raises this 404 when the requested image name has no row in the image_records store. It runs on the refusal path of `assert_image_read_access` so a truly deleted image returns 404 instead of 403, telling clients the reference is permanently gone.","triggerScenarios":"GET of image metadata/URL/full file (e.g. /v1/images/i/{image_name}) with a UUID name that no longer exists in the database — typically after the image was deleted or pruned, while the record lookup in read-access checking fails first.","commonSituations":"A UI or workflow holds a reference to an image that was deleted (or its queue history pruned); stale browser tabs; shared links to images removed by cleanup jobs.","solutions":["Drop the stale image reference and pick a different image; deletion is permanent","Re-generate the image (re-run the workflow/queue item) to recreate a new image name","Check the database/store points at the correct storage backend (misconfigured output/DB path can make all images look missing)","Confirm with an admin whether the image was removed by a pruning job (max_queue_history / disk cleanup)"],"exampleFix":"// before\nconst url = `/v1/images/i/${imageName}/full`;\nawait fetch(url); // 404 if deleted\n// after\nif (imageDeleted(imageName)) removeLayerFromCanvas(imageName);\nelse await fetch(`/v1/images/i/${imageName}/full`);","handlingStrategy":"try-catch","validationCode":"// Check existence before dereferencing\nconst exists = await fetch(`/v1/images/i/${imageName}/metadata`, {headers: authHeaders}).then(r => r.ok);","typeGuard":null,"tryCatchPattern":"try {\n  const meta = await fetch(`/v1/images/i/${imageName}/metadata`, {headers: authHeaders}).then(r => {\n    if (r.status === 404) throw new NotFoundError(imageName);\n    if (!r.ok) throw new Error('fetch failed');\n    return r.json();\n  });\n} catch (e) {\n  if (e instanceof NotFoundError) removeImageReference(e.name); // 404 = permanently gone\n  else throw e;\n}","preventionTips":["Treat 404 on images as permanent and prune the reference from workflows/canvas","Avoid persisting raw image names across cleanup/pruning operations without revalidation","Revalidate image names after restores, migrations, or version upgrades","Distinguish 404 (gone) from 403 (denied) — never delete references on 403"],"tags":["http-404","image","not-found","deleted-resource"],"backgroundTag":"resource-not-found-404","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}