{"record":{"id":"5fb02b24f06dc66f","repo":"supabase/supabase","slug":"body-message-5fb02b","errorCode":null,"errorMessage":"body?.message","messagePattern":"body\\?\\.message","errorType":"exception","errorClass":"ResponseError","httpStatus":null,"severity":"error","filePath":"apps/studio/data/ai/sql-title-mutation.ts","lineNumber":35,"sourceCode":"async function generateSqlTitle({ sql }: SqlTitleGenerateVariables) {\n  const url = `${BASE_PATH}/api/ai/sql/title-v2`\n\n  const headers = await constructHeaders({ 'Content-Type': 'application/json' })\n  const response = await fetchHandler(url, {\n    headers,\n    method: 'POST',\n    body: JSON.stringify({\n      sql,\n    }),\n  })\n  let body: any\n\n  try {\n    body = await response.json()\n  } catch {}\n\n  if (!response.ok) {\n    throw new ResponseError(body?.message, response.status)\n  }\n\n  return body as SqlTitleGenerateResponse\n}\n\ntype SqlTitleGenerateData = Awaited<ReturnType<typeof generateSqlTitle>>\n\nexport const useSqlTitleGenerateMutation = ({\n  onSuccess,\n  onError,\n  ...options\n}: Omit<\n  UseCustomMutationOptions<SqlTitleGenerateData, ResponseError, SqlTitleGenerateVariables>,\n  'mutationFn'\n> = {}) => {\n  return useMutation<SqlTitleGenerateData, ResponseError, SqlTitleGenerateVariables>({\n    mutationFn: (vars) => generateSqlTitle(vars),\n    async onError(data, variables, context) {","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/supabase/supabase/blob/beee91b9c2228dd57302dec75c733baaa84ab543/apps/studio/data/ai/sql-title-mutation.ts#L17-L53","documentation":"Thrown when POST /api/ai/sql/title returns non-2xx while auto-generating a title for a SQL snippet. body?.message is forwarded to ResponseError (undefined → generic fallback); the real HTTP status is preserved as ResponseError.code.","triggerScenarios":"Empty sql payload; AI service unavailable or timed out; rate-limited (429); session expired (401); route not deployed (404) in self-hosted builds; backend returns {error} instead of {message}.","commonSituations":"User saves a snippet with empty SQL; OpenAI quota exhausted; AI disabled; Cloudflare returns an HTML error page so the JSON parse is swallowed and body stays undefined.","solutions":["Guard the mutation to require a non-empty `sql` string before submitting.","Read ResponseError.code to branch behaviour (429 retry, 401 re-auth, 404 hide the feature).","Provide a fallback title (e.g. 'Untitled snippet') when generation fails so the save flow is not blocked.","Forward body.message but default to a readable string when absent."],"exampleFix":"// before\nif (!response.ok) {\n  throw new ResponseError(body?.message, response.status)\n}\n\n// after\nif (!response.ok) {\n  throw new ResponseError(\n    body?.message || `Failed to generate title (HTTP ${response.status})`,\n    response.status\n  )\n}","handlingStrategy":"try-catch","validationCode":"if (!sql || sql.trim().length === 0) {\n  throw new Error('Write some SQL before generating a title.')\n}","typeGuard":"function isRetryable(e: unknown): e is ResponseError {\n  return e instanceof ResponseError && (e.code === 429 || (e.code ?? 0) >= 500)\n}","tryCatchPattern":"try {\n  const { title } = await generateSqlTitle({ sql })\n} catch (e) {\n  // fall back so the save flow is not blocked\n  setTitle('Untitled snippet')\n  toast.error(e instanceof Error ? e.message : 'Could not generate title')\n}","preventionTips":["Require non-empty SQL before enabling the generate-title action.","Fall back to a default title on failure so the save flow continues.","Distinguish 401/429/404 by error.code."],"tags":["ai","mutation","response-error","sql-editor"],"backgroundTag":null,"analyzedSha":"beee91b9c2228dd57302dec75c733baaa84ab543","analyzedAt":"2026-08-12T06:51:48.935Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}