{"record":{"id":"9942000d803b4887","repo":"mckaywrigley/chatbot-ui","slug":"error-downloading-message-image","errorCode":null,"errorMessage":"Error downloading message image","messagePattern":"Error downloading message image","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db/storage/message-images.ts","lineNumber":29,"sourceCode":"\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":11,"sourceCodeEnd":34,"githubUrl":"https://github.com/mckaywrigley/chatbot-ui/blob/81328b61d2a4ab597a7a057be70e785cf756d9f8/db/storage/message-images.ts#L11-L34","documentation":"Thrown by getMessageImageFromStorage when createSignedUrl(filePath, 86400) on the 'message_images' bucket returns an error. Supabase fails signed-url creation when the object doesn't exist at filePath or the caller's role can't SELECT it. Unlike getAssistantImageFromStorage, this version throws to the caller rather than swallowing the error.","triggerScenarios":"Loading a chat message whose image path was deleted from storage, was written to a different bucket/project, or isn't covered by a storage.objects SELECT policy for the current (possibly anon) role; passing an empty or malformed path string.","commonSituations":"Old messages referencing purged objects (retention jobs, manual bucket cleanup); private bucket without SELECT policies; rendering history while logged out where anon role can't read; environment mismatch between DB rows and storage bucket contents.","solutions":["Verify the object exists at the logged path in the Supabase storage dashboard.","Add a storage.objects SELECT policy on message_images for authenticated users (or make the bucket public if appropriate).","Wrap the call and fall back to a placeholder image when signing fails, so one bad message doesn't break the whole chat render.","Audit for code paths that delete message images while messages still reference them."],"exampleFix":"// before\nconst url = await getMessageImageFromStorage(path)\n\n// after\nlet url: string | null = null\ntry {\n  url = await getMessageImageFromStorage(path)\n} catch {\n  url = null // render placeholder\n}","handlingStrategy":"fallback","validationCode":"if (!filePath) return placeholderUrl","typeGuard":"const isSignablePath = (p: unknown): p is string =>\n  typeof p === \"string\" && p.length > 0 && !p.startsWith(\"/\")","tryCatchPattern":"let url: string | null = null\ntry {\n  url = await getMessageImageFromStorage(path)\n} catch {\n  url = null // render image placeholder in the chat bubble\n}","preventionTips":["Never render message history without guarding signed-url failures — one missing object shouldn't break the thread.","Keep SELECT policies on message_images aligned with who may view messages.","Re-sign URLs on demand; never cache them beyond their 24h validity."],"tags":["supabase","storage","signed-url","message-images"],"backgroundTag":"supabase-signed-url-failed","analyzedSha":"81328b61d2a4ab597a7a057be70e785cf756d9f8","analyzedAt":"2026-08-27T19:24:42.689Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}