{"record":{"id":"19ef97d088dbb37d","repo":"mckaywrigley/chatbot-ui","slug":"error-uploading-image-19ef97","errorCode":null,"errorMessage":"Error uploading image","messagePattern":"Error uploading image","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db/storage/message-images.ts","lineNumber":17,"sourceCode":"import { supabase } from \"@/lib/supabase/browser-client\"\n\nexport const uploadMessageImage = async (path: string, image: File) => {\n  const bucket = \"message_images\"\n\n  const imageSizeLimit = 6000000 // 6MB\n\n  if (image.size > imageSizeLimit) {\n    throw new Error(`Image must be less than ${imageSizeLimit / 1000000}MB`)\n  }\n\n  const { error } = await supabase.storage.from(bucket).upload(path, image, {\n    upsert: true\n  })\n\n  if (error) {\n    throw new Error(\"Error uploading image\")\n  }\n\n  return path\n}\n\nexport const getMessageImageFromStorage = async (filePath: string) => {\n  const { data, error } = await supabase.storage\n    .from(\"message_images\")\n    .createSignedUrl(filePath, 60 * 60 * 24) // 24hrs\n\n  if (error) {\n    throw new Error(\"Error downloading message image\")\n  }\n\n  return data.signedUrl\n}\n","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/mckaywrigley/chatbot-ui/blob/81328b61d2a4ab597a7a057be70e785cf756d9f8/db/storage/message-images.ts#L1-L34","documentation":"Thrown by uploadMessageImage when supabase.storage.from(\"message_images\").upload(path, image, { upsert: true }) returns an error. The StorageError's real message (bucket not found, RLS violation, duplicate, size/type rejection) is masked by this generic string. Because upsert is enabled, an existing object at the same path must be updatable, not just insertable.","triggerScenarios":"The 'message_images' bucket doesn't exist in the project; the caller's role fails an INSERT or UPDATE storage policy for the given path; session expired so requests run as anon; or the path collides with an object owned by another user and upsert is denied.","commonSituations":"Bucket created in dev but not prod (or vice versa); storage policies only granting INSERT so upsert-overwrite fails with 'Duplicate'; missing auth session refresh in long-lived tabs; wrong supabase project env vars after forking the app.","solutions":["Log the error object to surface error.message ('Bucket not found', 'row-level security', 'Duplicate').","Create the 'message_images' bucket and grant INSERT + UPDATE policies to the object owner (auth.uid() = (storage.foldername(name))[1]).","Ensure the user has a fresh authenticated session before uploading.","Confirm the client is pointed at the project containing the bucket (check NEXT_PUBLIC_ env vars)."],"exampleFix":"// before\nif (error) {\n  throw new Error(\"Error uploading image\")\n}\n\n// after\nif (error) {\n  throw new Error(`Error uploading image to message_images: ${error.message}`)\n}","handlingStrategy":"retry","validationCode":"const { data: s } = await supabase.auth.getSession()\nif (!s.session) throw new Error(\"Sign in before sending images\")","typeGuard":"const isUploadedImage = (r: unknown): r is string =>\n  typeof r === \"string\" && r.length > 0","tryCatchPattern":"for (let i = 0; i < 3; i++) {\n  const { error } = await supabase.storage.from(\"message_images\").upload(path, image, { upsert: true })\n  if (!error) break\n  if (!/network|fetch|timeout/i.test(error.message)) throw new Error(error.message)\n  await new Promise(r => setTimeout(r, 400 * 2 ** i))\n}","preventionTips":["Ship bucket + policy creation in a setup migration so every environment has 'message_images'.","Grant both INSERT and UPDATE policies since upsert: true requires both.","Guard uploads with an active session check."],"tags":["supabase","storage","upload","rls","message-images"],"backgroundTag":"supabase-storage-upload-failed","analyzedSha":"81328b61d2a4ab597a7a057be70e785cf756d9f8","analyzedAt":"2026-08-27T19:24:42.689Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}