supabase/supabase · error · ResponseError

body?.message

Error message

body?.message

What it means

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.

Source

Thrown at apps/studio/data/ai/sql-title-mutation.ts:35

async function generateSqlTitle({ sql }: SqlTitleGenerateVariables) {
  const url = `${BASE_PATH}/api/ai/sql/title-v2`

  const headers = await constructHeaders({ 'Content-Type': 'application/json' })
  const response = await fetchHandler(url, {
    headers,
    method: 'POST',
    body: JSON.stringify({
      sql,
    }),
  })
  let body: any

  try {
    body = await response.json()
  } catch {}

  if (!response.ok) {
    throw new ResponseError(body?.message, response.status)
  }

  return body as SqlTitleGenerateResponse
}

type SqlTitleGenerateData = Awaited<ReturnType<typeof generateSqlTitle>>

export const useSqlTitleGenerateMutation = ({
  onSuccess,
  onError,
  ...options
}: Omit<
  UseCustomMutationOptions<SqlTitleGenerateData, ResponseError, SqlTitleGenerateVariables>,
  'mutationFn'
> = {}) => {
  return useMutation<SqlTitleGenerateData, ResponseError, SqlTitleGenerateVariables>({
    mutationFn: (vars) => generateSqlTitle(vars),
    async onError(data, variables, context) {

View on GitHub (pinned to beee91b9c2)

Solutions

  1. Guard the mutation to require a non-empty `sql` string before submitting.
  2. Read ResponseError.code to branch behaviour (429 retry, 401 re-auth, 404 hide the feature).
  3. Provide a fallback title (e.g. 'Untitled snippet') when generation fails so the save flow is not blocked.
  4. Forward body.message but default to a readable string when absent.

Example fix

// before
if (!response.ok) {
  throw new ResponseError(body?.message, response.status)
}

// after
if (!response.ok) {
  throw new ResponseError(
    body?.message || `Failed to generate title (HTTP ${response.status})`,
    response.status
  )
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!sql || sql.trim().length === 0) {
  throw new Error('Write some SQL before generating a title.')
}

Type guard

function isRetryable(e: unknown): e is ResponseError {
  return e instanceof ResponseError && (e.code === 429 || (e.code ?? 0) >= 500)
}

Try / catch

try {
  const { title } = await generateSqlTitle({ sql })
} catch (e) {
  // fall back so the save flow is not blocked
  setTitle('Untitled snippet')
  toast.error(e instanceof Error ? e.message : 'Could not generate title')
}

Prevention

When it happens

Trigger: 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}.

Common situations: 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.

Related errors


AI-assisted analysis of supabase/supabase@beee91b9c2 (2026-08-12). Data as JSON: /api/errors/5fb02b24f06dc66f. Report an issue: GitHub.