{"record":{"id":"57701211d84dbd4f","repo":"mckaywrigley/chatbot-ui","slug":"error-downloading-file","errorCode":null,"errorMessage":"Error downloading file","messagePattern":"Error downloading file","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db/storage/files.ts","lineNumber":53,"sourceCode":"}\n\nexport const deleteFileFromStorage = async (filePath: string) => {\n  const { error } = await supabase.storage.from(\"files\").remove([filePath])\n\n  if (error) {\n    toast.error(\"Failed to remove file!\")\n    return\n  }\n}\n\nexport const getFileFromStorage = async (filePath: string) => {\n  const { data, error } = await supabase.storage\n    .from(\"files\")\n    .createSignedUrl(filePath, 60 * 60 * 24) // 24hrs\n\n  if (error) {\n    console.error(`Error uploading file with path: ${filePath}`, error)\n    throw new Error(\"Error downloading file\")\n  }\n\n  return data.signedUrl\n}\n","sourceCodeStart":35,"sourceCodeEnd":58,"githubUrl":"https://github.com/mckaywrigley/chatbot-ui/blob/81328b61d2a4ab597a7a057be70e785cf756d9f8/db/storage/files.ts#L35-L58","documentation":"Thrown by getFileFromStorage when createSignedUrl(filePath, 86400) on the 'files' bucket returns an error. Signed URL generation fails when the object doesn't exist at that path or the caller lacks SELECT access. The function does log the underlying error with the path before throwing, so server/console output shows the real StorageError.","triggerScenarios":"Requesting a signed URL for a path that was deleted (deleteFileFromStorage ran first), a stale base64(file_id) path after a record was recreated, a path from a different environment's bucket, or a private 'files' bucket with no SELECT policy for the requesting role.","commonSituations":"Chat history referencing attachments whose storage objects were removed; multi-environment DBs sharing rows but not buckets; missing storage.objects SELECT policy; expired session when policies require authenticated users.","solutions":["Read the console error logged just before the throw — it includes filePath and the StorageError message.","Verify the object exists at that path in the Supabase dashboard (Storage > files).","Add a storage.objects SELECT policy on 'files' for authenticated users/owners.","If objects can be deleted out-of-band, degrade gracefully (fallback placeholder) instead of throwing."],"exampleFix":"// before\nif (error) {\n  console.error(`Error uploading file with path: ${filePath}`, error)\n  throw new Error(\"Error downloading file\")\n}\n\n// after\nif (error) {\n  console.error(`Error signing URL for path: ${filePath}`, error)\n  return null // let the caller show a fallback\n}","handlingStrategy":"fallback","validationCode":"const { data } = await supabase.storage.from(\"files\").list(folder(path))\nif (!data?.some(o => o.name === name(path))) return placeholderUrl","typeGuard":"const hasSignedUrl = (r: unknown): r is { signedUrl: string } =>\n  !!r && typeof (r as { signedUrl?: string }).signedUrl === \"string\"","tryCatchPattern":"try {\n  url = await getFileFromStorage(path)\n} catch {\n  url = null // show 'file unavailable' in UI instead of crashing the message list\n}","preventionTips":["Fall back to a placeholder for deleted attachments rather than throwing mid-render.","Keep DB rows and storage objects lifecycle in sync: delete rows when objects are purged (or vice versa).","Maintain SELECT storage policies on the files bucket."],"tags":["supabase","storage","signed-url","files"],"backgroundTag":"supabase-signed-url-failed","analyzedSha":"81328b61d2a4ab597a7a057be70e785cf756d9f8","analyzedAt":"2026-08-27T19:24:42.689Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}