diff --git a/ui/app/routes/api-keys/route.tsx b/ui/app/routes/api-keys/route.tsx
index 5324ed74fe..f500c1e7b9 100644
--- a/ui/app/routes/api-keys/route.tsx
+++ b/ui/app/routes/api-keys/route.tsx
@@ -15,7 +15,7 @@ import {
SectionLayout,
} from "~/components/layout/PageLayout";
import { logger } from "~/utils/logger";
-import { PageErrorContent } from "~/components/ui/error";
+import { LayoutErrorBoundary, PageErrorContent } from "~/components/ui/error";
import {
getPostgresClient,
isPostgresAvailable,
@@ -314,6 +314,5 @@ export default function ApiKeysPage({ loaderData }: Route.ComponentProps) {
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- logger.error(error);
- return ;
+ return ;
}
diff --git a/ui/app/routes/autopilot/sessions/$session_id/route.tsx b/ui/app/routes/autopilot/sessions/$session_id/route.tsx
index 96d893aecf..91ee2e326d 100644
--- a/ui/app/routes/autopilot/sessions/$session_id/route.tsx
+++ b/ui/app/routes/autopilot/sessions/$session_id/route.tsx
@@ -10,7 +10,6 @@ import {
import {
Await,
data,
- isRouteErrorResponse,
useAsyncError,
useFetcher,
useNavigate,
@@ -33,6 +32,7 @@ import { useElementHeight } from "~/hooks/useElementHeight";
import { useInfiniteScrollUp } from "~/hooks/use-infinite-scroll-up";
import type { AutopilotStatus, GatewayEvent } from "~/types/tensorzero";
import { useToast } from "~/hooks/use-toast";
+import { LayoutErrorBoundary } from "~/components/ui/error/LayoutErrorBoundary";
import { SectionErrorNotice } from "~/components/ui/error/ErrorContentPrimitives";
import { getFeatureFlags } from "~/utils/feature_flags";
@@ -810,29 +810,5 @@ export default function AutopilotSessionEventsPage({
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- logger.error(error);
-
- if (isRouteErrorResponse(error)) {
- return (
-
-
- {error.status} {error.statusText}
-
-
{error.data}
-
- );
- } else if (error instanceof Error) {
- return (
-
-
Error
-
{error.message}
-
- );
- } else {
- return (
-
-
Unknown Error
-
- );
- }
+ return ;
}
diff --git a/ui/app/routes/autopilot/sessions/route.tsx b/ui/app/routes/autopilot/sessions/route.tsx
index 0319268cfc..195c761444 100644
--- a/ui/app/routes/autopilot/sessions/route.tsx
+++ b/ui/app/routes/autopilot/sessions/route.tsx
@@ -1,12 +1,7 @@
import { Plus } from "lucide-react";
import { Suspense, use } from "react";
import type { Route } from "./+types/route";
-import {
- data,
- isRouteErrorResponse,
- useLocation,
- useNavigate,
-} from "react-router";
+import { data, useLocation, useNavigate } from "react-router";
import { useTensorZeroStatusFetcher } from "~/routes/api/tensorzero/status";
import {
PageHeader,
@@ -16,7 +11,7 @@ import {
import { ActionBar } from "~/components/layout/ActionBar";
import { Button } from "~/components/ui/button";
import PageButtons from "~/components/utils/PageButtons";
-import { logger } from "~/utils/logger";
+import { LayoutErrorBoundary } from "~/components/ui/error/LayoutErrorBoundary";
import { SessionsTableRows } from "../AutopilotSessionsTable";
import { getAutopilotClient } from "~/utils/tensorzero.server";
import type { Session } from "~/types/tensorzero";
@@ -214,29 +209,5 @@ export default function AutopilotSessionsPage({
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- logger.error(error);
-
- if (isRouteErrorResponse(error)) {
- return (
-
-
- {error.status} {error.statusText}
-
-
{error.data}
-
- );
- } else if (error instanceof Error) {
- return (
-
-
Error
-
{error.message}
-
- );
- } else {
- return (
-
-
Unknown Error
-
- );
- }
+ return ;
}
diff --git a/ui/app/routes/evaluations/$evaluation_name/$datapoint_id/route.tsx b/ui/app/routes/evaluations/$evaluation_name/$datapoint_id/route.tsx
index d67fedbb1f..4afdfdcbe8 100644
--- a/ui/app/routes/evaluations/$evaluation_name/$datapoint_id/route.tsx
+++ b/ui/app/routes/evaluations/$evaluation_name/$datapoint_id/route.tsx
@@ -17,13 +17,13 @@ import { getTensorZeroClient } from "~/utils/tensorzero.server";
import {
Await,
data,
- isRouteErrorResponse,
Link,
redirect,
useFetcher,
useLocation,
type RouteHandle,
} from "react-router";
+import { LayoutErrorBoundary } from "~/components/ui/error/LayoutErrorBoundary";
import { Suspense } from "react";
import { InputElement } from "~/components/input_output/InputElement";
import { ChatOutputElement } from "~/components/input_output/ChatOutputElement";
@@ -662,31 +662,7 @@ const MetricRow = ({
};
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- logger.error(error);
-
- if (isRouteErrorResponse(error)) {
- return (
-
-
- {error.status} {error.statusText}
-
-
{error.data}
-
- );
- } else if (error instanceof Error) {
- return (
-
-
Error
-
{error.message}
-
- );
- } else {
- return (
-
-
Unknown Error
-
- );
- }
+ return ;
}
type OutputsSectionProps = {
diff --git a/ui/app/routes/observability/functions/route.tsx b/ui/app/routes/observability/functions/route.tsx
index 20154bac45..8794f2b336 100644
--- a/ui/app/routes/observability/functions/route.tsx
+++ b/ui/app/routes/observability/functions/route.tsx
@@ -1,5 +1,4 @@
import type { Route } from "./+types/route";
-import { isRouteErrorResponse } from "react-router";
import FunctionsTable from "./FunctionsTable";
import { useConfig } from "~/context/config";
import {
@@ -7,7 +6,7 @@ import {
PageLayout,
SectionLayout,
} from "~/components/layout/PageLayout";
-import { logger } from "~/utils/logger";
+import { LayoutErrorBoundary } from "~/components/ui/error/LayoutErrorBoundary";
import { useMemo, useState } from "react";
import { getTensorZeroClient } from "~/utils/tensorzero.server";
@@ -66,29 +65,5 @@ export default function FunctionsPage({ loaderData }: Route.ComponentProps) {
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- logger.error(error);
-
- if (isRouteErrorResponse(error)) {
- return (
-
-
- {error.status} {error.statusText}
-
-
{error.data}
-
- );
- } else if (error instanceof Error) {
- return (
-
-
Error
-
{error.message}
-
- );
- } else {
- return (
-
-
Unknown Error
-
- );
- }
+ return ;
}
diff --git a/ui/app/routes/playground/route.tsx b/ui/app/routes/playground/route.tsx
index 1d010a77b7..130f6efe84 100644
--- a/ui/app/routes/playground/route.tsx
+++ b/ui/app/routes/playground/route.tsx
@@ -4,9 +4,9 @@ import {
Link,
type RouteHandle,
type ShouldRevalidateFunctionArgs,
- isRouteErrorResponse,
useNavigation,
} from "react-router";
+import { LayoutErrorBoundary } from "~/components/ui/error/LayoutErrorBoundary";
import { DatasetCombobox } from "~/components/dataset/DatasetCombobox";
import { FunctionSelector } from "~/components/function/FunctionSelector";
import { PageHeader, PageLayout } from "~/components/layout/PageLayout";
@@ -575,70 +575,7 @@ export default function PlaygroundPage({ loaderData }: Route.ComponentProps) {
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- if (isRouteErrorResponse(error)) {
- return (
-
-
-
-
- {error.status} {error.statusText}
-
-
{error.data}
-
- Go to Playground
-
-
-
-
- );
- } else if (error instanceof Error) {
- return (
-
-
-
-
Error
-
{error.message}
-
-
- Stack trace
-
-
- {error.stack}
-
-
-
- Go to Playground
-
-
-
-
- );
- } else {
- return (
-
-
-
-
Unknown Error
-
- An unexpected error occurred. Please try again.
-
-
- Go to Playground
-
-
-
-
- );
- }
+ return ;
}
function GridRow({
diff --git a/ui/app/utils/tensorzero/errors.ts b/ui/app/utils/tensorzero/errors.ts
index 26f06c5c00..6a6eee0b40 100644
--- a/ui/app/utils/tensorzero/errors.ts
+++ b/ui/app/utils/tensorzero/errors.ts
@@ -824,59 +824,77 @@ export interface PageErrorInfo {
/**
* Returns user-friendly error info for page-level errors.
- * Maps status codes to sanitized messages - never exposes raw server strings.
+ * Uses custom error.data message if provided (string), otherwise falls back
+ * to generic status-based messages.
*/
export function getPageErrorInfo(error: unknown): PageErrorInfo {
if (isRouteErrorResponse(error)) {
+ // Use custom message from error.data if it's a string
+ const customMessage =
+ typeof error.data === "string" && error.data.length > 0
+ ? error.data
+ : null;
+
switch (error.status) {
case 400:
return {
title: "Bad Request",
message:
+ customMessage ??
"The request was invalid. Please check your input and try again.",
status: 400,
};
case 401:
return {
title: "Unauthorized",
- message: "Authentication is required to access this resource.",
+ message:
+ customMessage ??
+ "Authentication is required to access this resource.",
status: 401,
};
case 403:
return {
title: "Forbidden",
- message: "You don't have permission to access this resource.",
+ message:
+ customMessage ??
+ "You don't have permission to access this resource.",
status: 403,
};
case 404:
return {
title: "Not Found",
- message: "The requested resource could not be found.",
+ message:
+ customMessage ?? "The requested resource could not be found.",
status: 404,
};
case 500:
return {
title: "Server Error",
- message: "The server encountered an error. Please try again later.",
+ message:
+ customMessage ??
+ "The server encountered an error. Please try again later.",
status: 500,
};
case 502:
return {
title: "Bad Gateway",
- message: "Unable to reach the server. Please try again later.",
+ message:
+ customMessage ??
+ "Unable to reach the server. Please try again later.",
status: 502,
};
case 503:
return {
title: "Service Unavailable",
message:
+ customMessage ??
"The service is temporarily unavailable. Please try again later.",
status: 503,
};
default:
return {
title: "Error",
- message: "An unexpected error occurred.",
+ message: customMessage ?? "An unexpected error occurred.",
status: error.status,
};
}