fix(studio): batch of production Sentry crash fixes (array/null guards) (#47460)

Fixes a batch of production Studio crashes from Sentry (all caught by
the global error boundary). Most are missing array/null guards where an
endpoint typed as an array — or with a nested array field — returned a
non-array body in production; a few are one-off render crashes.

Resolves FE-3748.

## Issues fixed

| Sentry | Error | Fix |
| --- | --- | --- |
| [J7R](https://supabase.sentry.io/issues/7492997940/) | Maximum update
depth exceeded | Disable RadialBar animation in disk-cooldown countdown
|
| [JR5](https://supabase.sentry.io/issues/7548484681/) |
resourceWarnings.find is not a function | Guard in
ResourceExhaustionWarningBanner |
| [JCJ](https://supabase.sentry.io/issues/7506024989/) |
resourceWarnings.find is not a function | Guard in ProjectLayout +
normalize query |
| [K1Y](https://supabase.sentry.io/issues/7584792331/) | snippet.name on
undefined | Optional-chain SQL editor download filename |
| [B3K](https://supabase.sentry.io/issues/7141649636/) |
pagination.count on undefined | Guard pagination in projects infinite
query |
| [JVP](https://supabase.sentry.io/issues/7560437621/) | schemas.some /
extensions.find | Coerce pg-meta lists to arrays in
useInstalledIntegrations |
| [JR2](https://supabase.sentry.io/issues/7548339272/) | extensions.find
is not a function | (same fix as JVP) |
| [JQR](https://supabase.sentry.io/issues/7547163939/) | lints.filter is
not a function | Normalize project lints query |
| [JR3](https://supabase.sentry.io/issues/7548433501/) |
entitlements.find is not a function | Guard call sites + normalize
entitlements query |
| [JQS](https://supabase.sentry.io/issues/7547557098/) |
selected_addons.find is not a function | Normalize addons query arrays |


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved stability across several Studio screens by handling missing
or unexpected data more safely.
* Downloads now use a fallback name when a snippet name isn’t available.
* Project, entitlement, schema, addon, warning, and extension views are
less likely to break when data is missing or not in the expected format.
* Pagination and countdown visuals now behave more consistently, with
reduced chance of runtime errors or animation-related glitches.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
Ali WaseemandIvan Vasilov authored and GitHub committed 2026-07-02 08:38:17 +00:00
1 parent 5c27710c2b
commit 3fcf980b0a
13 files changed
+30 -16

No files matched your search

@@ -69,7 +69,7 @@ export const useInstalledIntegrations = () => {
connectionString: project?.connectionString,
})
const isHooksEnabled = schemas?.some((schema) => schema.name === 'supabase_functions')
const isHooksEnabled = schemas.some((schema) => schema.name === 'supabase_functions')
const installedIntegrations = useMemo(() => {
return allIntegrations
@@ -153,7 +153,7 @@ export const UtilityPanel = ({
<DownloadResultsButton
variant="text"
results={result.rows as any[]}
fileName={`Supabase Snippet ${snippet.name}`}
fileName={`Supabase Snippet ${snippet?.name ?? 'Results'}`}
onDownloadAsCSV={() => track('sql_editor_result_download_csv_clicked')}
onCopyAsMarkdown={() => track('sql_editor_result_copy_markdown_clicked')}
onCopyAsJSON={() => track('sql_editor_result_copy_json_clicked')}
@@ -142,7 +142,7 @@ export const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<Projec
const { data: resourceWarnings } = useResourceWarningsQuery({
slug: selectedOrganization?.slug,
})
const projectResourceWarnings = resourceWarnings?.find(
const projectResourceWarnings = (Array.isArray(resourceWarnings) ? resourceWarnings : []).find(
(w) => w.project === selectedProject?.ref
)
const isComputeNearExhaustion =
@@ -35,7 +35,7 @@ const CountdownTimerRadial = ({ progress }: CountdownTimerRadialProps) => {
className="first:fill-foreground-muted/50 last:fill-background-200"
polarRadius={[16, 11]}
/>
<RadialBar dataKey="timeRemaining" cornerRadius={2} />
<RadialBar dataKey="timeRemaining" cornerRadius={2} isAnimationActive={false} />
</RadialBarChart>
</ChartContainer>
@@ -48,7 +48,7 @@ const CountdownTimerRadial = ({ progress }: CountdownTimerRadialProps) => {
innerRadius={14}
outerRadius={5}
>
<RadialBar dataKey="hand" cornerRadius={2} isAnimationActive={true} />
<RadialBar dataKey="hand" cornerRadius={2} isAnimationActive={false} />
</RadialBarChart>
</ChartContainer>
</div>
@@ -6,6 +6,7 @@ import { databaseExtensionsKeys } from './keys'
import { executeSql } from '@/data/sql/execute-sql-mutation'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { PROJECT_STATUS } from '@/lib/constants'
import { EMPTY_ARR } from '@/lib/void'
import type { ResponseError, UseCustomQueryOptions } from '@/types'
export type DatabaseExtension = components['schemas']['PostgresExtension'] & {
@@ -26,7 +27,7 @@ export async function getDatabaseExtensions(
{ projectRef, connectionString, sql, queryKey: ['database-extensions'] },
signal
)
return result as DatabaseExtension[]
return (Array.isArray(result) ? result : EMPTY_ARR) as DatabaseExtension[]
}
export type DatabaseExtensionsData = Awaited<ReturnType<typeof getDatabaseExtensions>>
+2 -1
View File
@@ -4,6 +4,7 @@ import { z } from 'zod'
import { databaseKeys } from './keys'
import { executeSql } from '@/data/sql/execute-sql-mutation'
import { EMPTY_ARR } from '@/lib/void'
import { ResponseError, UseCustomQueryOptions } from '@/types'
export type SchemasVariables = {
@@ -32,7 +33,7 @@ export async function getSchemas(
signal
)
return result
return Array.isArray(result) ? result : EMPTY_ARR
}
export const useSchemasQuery = <TData = SchemasData>(
@@ -3,6 +3,7 @@ import type { components } from 'api-types'
import { get, handleError } from '@/data/fetchers'
import { organizationKeys } from '@/data/organizations/keys'
import { EMPTY_ARR } from '@/lib/void'
import { UseCustomQueryOptions } from '@/types'
import { ResponseError } from '@/types/base'
@@ -32,7 +33,10 @@ export async function getEntitlements(
})
if (error) handleError(error)
return data
return {
...data,
entitlements: Array.isArray(data?.entitlements) ? data.entitlements : EMPTY_ARR,
}
}
export type EntitlementsData = Awaited<ReturnType<typeof getEntitlements>>
+2 -1
View File
@@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query'
import { fdwKeys } from './keys'
import { executeSql } from '@/data/sql/execute-sql-mutation'
import { EMPTY_ARR } from '@/lib/void'
import { ResponseError, UseCustomQueryOptions } from '@/types'
export type FDWColumn = {
@@ -43,7 +44,7 @@ export async function getFDWs(
signal
)
return result as FDW[]
return (Array.isArray(result) ? result : EMPTY_ARR) as FDW[]
}
export type FDWsData = Awaited<ReturnType<typeof getFDWs>>
+2 -1
View File
@@ -5,6 +5,7 @@ import { lintKeys } from './keys'
import { get, handleError } from '@/data/fetchers'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { PROJECT_STATUS } from '@/lib/constants'
import { EMPTY_ARR } from '@/lib/void'
import type { ResponseError, UseCustomQueryOptions } from '@/types'
type ProjectLintsVariables = {
@@ -24,7 +25,7 @@ export async function getProjectLints({ projectRef }: ProjectLintsVariables, sig
if (error) handleError(error)
return data
return Array.isArray(data) ? data : EMPTY_ARR
}
export type ProjectLintsData = Awaited<ReturnType<typeof getProjectLints>>
@@ -70,9 +70,9 @@ export const useProjectsInfiniteQuery = <TData = ProjectsInfiniteData>(
const page = pages.length
const currentTotalCount = page * limit
// @ts-ignore [Joshen] API type issue for Version 2 endpoints
const totalCount = lastPage.pagination.count
const totalCount = lastPage?.pagination?.count
if (currentTotalCount >= totalCount) return undefined
if (totalCount === undefined || currentTotalCount >= totalCount) return undefined
return page
},
...options,
@@ -3,6 +3,7 @@ import { useQuery } from '@tanstack/react-query'
import { subscriptionKeys } from './keys'
import { get, handleError } from '@/data/fetchers'
import { IS_PLATFORM } from '@/lib/constants'
import { EMPTY_ARR } from '@/lib/void'
import type { ResponseError, UseCustomQueryOptions } from '@/types'
export type ProjectAddonsVariables = {
@@ -24,7 +25,11 @@ export async function getProjectAddons(
})
if (error) handleError(error)
return data
return {
...data,
selected_addons: Array.isArray(data?.selected_addons) ? data.selected_addons : EMPTY_ARR,
available_addons: Array.isArray(data?.available_addons) ? data.available_addons : EMPTY_ARR,
}
}
export type ProjectAddonsData = Awaited<ReturnType<typeof getProjectAddons>>
@@ -4,6 +4,7 @@ import { IS_PLATFORM } from 'common'
import { usageKeys } from './keys'
import type { components } from '@/data/api'
import { get, handleError } from '@/data/fetchers'
import { EMPTY_ARR } from '@/lib/void'
import type { ResponseError, UseCustomQueryOptions } from '@/types'
export type ResourceWarningsVariables = {
@@ -26,7 +27,7 @@ export async function getResourceWarnings(
})
if (error) handleError(error)
return data
return Array.isArray(data) ? data : EMPTY_ARR
}
export type ResourceWarning = components['schemas']['ProjectResourceWarningsResponse']
@@ -69,7 +69,7 @@ export function useHasEntitlementAccess(organizationSlug?: string) {
return useCallback(
(key: string) =>
IS_PLATFORM
? (entitlementsData?.entitlements.find((e) => e.feature.key === key)?.hasAccess ?? false)
? (entitlementsData?.entitlements?.find((e) => e.feature.key === key)?.hasAccess ?? false)
: true,
[entitlementsData]
)
@@ -107,7 +107,7 @@ export function useCheckEntitlements(
// If no organization slug, no access
if (!finalOrgSlug) return { entitlement: null }
const entitlement = entitlementsData?.entitlements.find(
const entitlement = entitlementsData?.entitlements?.find(
(entitlement) => entitlement.feature.key === featureKey
)