{"record":{"id":"c53a32a4335ba9f8","repo":"mckaywrigley/chatbot-ui","slug":"error-message-c53a32","errorCode":null,"errorMessage":"error.message","messagePattern":"error\\.message","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"db/assistant-files.ts","lineNumber":18,"sourceCode":"import { supabase } from \"@/lib/supabase/browser-client\"\nimport { TablesInsert } from \"@/supabase/types\"\n\nexport const getAssistantFilesByAssistantId = async (assistantId: string) => {\n  const { data: assistantFiles, error } = await supabase\n    .from(\"assistants\")\n    .select(\n      `\n        id, \n        name, \n        files (*)\n      `\n    )\n    .eq(\"id\", assistantId)\n    .single()\n\n  if (!assistantFiles) {\n    throw new Error(error.message)\n  }\n\n  return assistantFiles\n}\n\nexport const createAssistantFile = async (\n  assistantFile: TablesInsert<\"assistant_files\">\n) => {\n  const { data: createdAssistantFile, error } = await supabase\n    .from(\"assistant_files\")\n    .insert(assistantFile)\n    .select(\"*\")\n\n  if (!createdAssistantFile) {\n    throw new Error(error.message)\n  }\n\n  return createdAssistantFile","sourceCodeStart":1,"sourceCodeEnd":36,"githubUrl":"https://github.com/mckaywrigley/chatbot-ui/blob/81328b61d2a4ab597a7a057be70e785cf756d9f8/db/assistant-files.ts#L1-L36","documentation":"getAssistantFilesByAssistantId selects a single row from a view/table keyed by assistant id and throws error.message when no data comes back. Because the code checks !assistantFiles rather than error, this throws even when the query legitimately returns zero rows — and in that case error is null, so error.message throws 'Cannot read properties of null' instead. It conflates 'not found' with 'query failed'.","triggerScenarios":"Passing an assistantId that has no files row (error is null → TypeError reading 'message'); an RLS SELECT policy hiding the row from the current user; a malformed UUID for id causing a PostgREST 22P02 invalid-input error.","commonSituations":"Fetching an assistant created before the assistant_files row existed; RLS enabled without a SELECT policy; integration tests using random IDs; .single() on an empty result set.","solutions":["Distinguish the two cases: check error first, then treat null data as 'not found' and return null or throw a dedicated NotFoundError","Verify the assistantId exists and the current user's RLS SELECT policy covers assistant_files","Validate assistantId is a UUID before querying","Re-run the query in the Supabase table editor to see whether the row is visible to your role"],"exampleFix":"// before\nif (!assistantFiles) {\n  throw new Error(error.message)\n}\n\n// after\nif (error) throw new Error(`getAssistantFiles failed: ${error.message}`)\nif (!assistantFiles) return null // or throw new NotFoundError(`No files for assistant ${assistantId}`)","handlingStrategy":"type-guard","validationCode":"const isUuid = (id: string) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)\nif (!isUuid(assistantId)) throw new Error('invalid assistantId')","typeGuard":"const hasFiles = (x: unknown): x is { files: unknown[] } =>\n  !!x && typeof x === 'object' && Array.isArray((x as any).files)","tryCatchPattern":"try { const files = await getAssistantFilesByAssistantId(id) } catch (e) { if (e instanceof TypeError) return [] /* not-found path */ throw e }","preventionTips":["Treat null data as empty, not exceptional","Patch the library to check error before data","Cache file lists per assistant to avoid repeated lookups"],"tags":["supabase","postgrest","not-found","single-row","type-mismatch"],"backgroundTag":"supabase-single-row-not-found","analyzedSha":"81328b61d2a4ab597a7a057be70e785cf756d9f8","analyzedAt":"2026-08-27T19:24:42.689Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}