From 42718faad2ab034a93d5f4dfef8f0e4aab017fef Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 18 Feb 2026 11:41:53 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AD=20fix:=20Robust=20404=20Conversati?= =?UTF-8?q?on=20Not=20Found=20Redirect=20(#11853)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: route to new conversation when conversation not found * Addressed PR feedback * fix: Robust 404 conversation redirect handling - Extract `isNotFoundError` utility to `utils/errors.ts` so axios stays contained in one place rather than leaking into route/query layers - Add `initialConvoQuery.isError` to the useEffect dependency array so the redirect actually fires when the 404 response arrives after other deps have already settled (was the root cause of the blank screen) - Show a warning toast so users understand why they were redirected - Add `com_ui_conversation_not_found` i18n key * fix: Enhance error handling in getResponseStatus function - Update the getResponseStatus function to ensure it correctly returns the status from error objects only if the status is a number. This improves robustness in error handling by preventing potential type issues. * fix: Improve conversation not found handling in ChatRoute - Enhance error handling when a conversation is not found by checking additional conditions before showing a warning toast. - Update the newConversation function to include model data and preset options, improving user experience during error scenarios. * fix: Log error details for conversation not found in ChatRoute - Added logging for the initial conversation query error when a conversation is not found, improving debugging capabilities and error tracking in the ChatRoute component. --------- Co-authored-by: Dan Lew --- client/src/data-provider/queries.ts | 8 +++++- client/src/locales/en/translation.json | 1 + client/src/routes/ChatRoute.tsx | 39 ++++++++++++++++++++++++-- client/src/utils/errors.ts | 21 ++++++++++++++ client/src/utils/index.ts | 1 + 5 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 client/src/utils/errors.ts diff --git a/client/src/data-provider/queries.ts b/client/src/data-provider/queries.ts index 8f2b702a3b..866e25a262 100644 --- a/client/src/data-provider/queries.ts +++ b/client/src/data-provider/queries.ts @@ -31,7 +31,7 @@ import type { SharedLinksResponse, } from 'librechat-data-provider'; import type { ConversationCursorData } from '~/utils/convos'; -import { findConversationInInfinite } from '~/utils'; +import { findConversationInInfinite, isNotFoundError } from '~/utils'; export const useGetPresetsQuery = ( config?: UseQueryOptions, @@ -71,6 +71,12 @@ export const useGetConvoIdQuery = ( refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false, + retry: (failureCount, error) => { + if (isNotFoundError(error)) { + return false; + } + return failureCount < 3; + }, ...config, }, ); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index a9f8805d9b..e0dad68431 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -842,6 +842,7 @@ "com_ui_controls": "Controls", "com_ui_conversation": "conversation", "com_ui_conversation_label": "{{title}} conversation", + "com_ui_conversation_not_found": "Conversation not found", "com_ui_conversations": "conversations", "com_ui_convo_archived": "Conversation archived", "com_ui_convo_delete_error": "Failed to delete conversation", diff --git a/client/src/routes/ChatRoute.tsx b/client/src/routes/ChatRoute.tsx index 0670ee1e85..c8fea73470 100644 --- a/client/src/routes/ChatRoute.tsx +++ b/client/src/routes/ChatRoute.tsx @@ -1,15 +1,22 @@ import { useEffect } from 'react'; -import { Spinner } from '@librechat/client'; import { useParams } from 'react-router-dom'; import { useRecoilCallback, useRecoilValue } from 'recoil'; +import { Spinner, useToastContext } from '@librechat/client'; import { Constants, EModelEndpoint } from 'librechat-data-provider'; import { useGetModelsQuery } from 'librechat-data-provider/react-query'; import type { TPreset } from 'librechat-data-provider'; +import { + useNewConvo, + useAppStartup, + useAssistantListMap, + useIdChangeEffect, + useLocalize, +} from '~/hooks'; import { useGetConvoIdQuery, useGetStartupConfig, useGetEndpointsQuery } from '~/data-provider'; -import { useNewConvo, useAppStartup, useAssistantListMap, useIdChangeEffect } from '~/hooks'; -import { getDefaultModelSpec, getModelSpecPreset, logger } from '~/utils'; +import { getDefaultModelSpec, getModelSpecPreset, logger, isNotFoundError } from '~/utils'; import { ToolCallsMapProvider } from '~/Providers'; import ChatView from '~/components/Chat/ChatView'; +import { NotificationSeverity } from '~/common'; import useAuthRedirect from './useAuthRedirect'; import temporaryStore from '~/store/temporary'; import store from '~/store'; @@ -33,6 +40,8 @@ export default function ChatRoute() { useIdChangeEffect(conversationId); const { hasSetConversation, conversation } = store.useCreateConversationAtom(index); const { newConversation } = useNewConvo(); + const { showToast } = useToastContext(); + const localize = useLocalize(); const modelsQuery = useGetModelsQuery({ enabled: isAuthenticated, @@ -92,6 +101,29 @@ export default function ChatRoute() { keepLatestMessage: true, }); hasSetConversation.current = true; + } else if ( + conversationId && + endpointsQuery.data && + modelsQuery.data && + initialConvoQuery.isError && + isNotFoundError(initialConvoQuery.error) + ) { + const result = getDefaultModelSpec(startupConfig); + const spec = result?.default ?? result?.last; + showToast({ + message: localize('com_ui_conversation_not_found'), + severity: NotificationSeverity.WARNING, + }); + logger.log( + 'conversation', + 'ChatRoute initialConvoQuery isNotFoundError', + initialConvoQuery.error, + ); + newConversation({ + modelsData: modelsQuery.data, + ...(spec ? { preset: getModelSpecPreset(spec) } : {}), + }); + hasSetConversation.current = true; } else if ( conversationId === Constants.NEW_CONVO && assistantListMap[EModelEndpoint.assistants] && @@ -125,6 +157,7 @@ export default function ChatRoute() { roles, startupConfig, initialConvoQuery.data, + initialConvoQuery.isError, endpointsQuery.data, modelsQuery.data, assistantListMap, diff --git a/client/src/utils/errors.ts b/client/src/utils/errors.ts new file mode 100644 index 0000000000..04666c5313 --- /dev/null +++ b/client/src/utils/errors.ts @@ -0,0 +1,21 @@ +import axios from 'axios'; + +/** + * Returns the HTTP response status code from an error, regardless of the + * HTTP client used. Handles Axios errors first, then falls back to checking + * for a plain `status` property so callers never need to import axios. + */ +export const getResponseStatus = (error: unknown): number | undefined => { + if (axios.isAxiosError(error)) { + return error.response?.status; + } + if (error != null && typeof error === 'object' && 'status' in error) { + const { status } = error as { status: unknown }; + if (typeof status === 'number') { + return status; + } + } + return undefined; +}; + +export const isNotFoundError = (error: unknown): boolean => getResponseStatus(error) === 404; diff --git a/client/src/utils/index.ts b/client/src/utils/index.ts index eede48f244..b8117b2677 100644 --- a/client/src/utils/index.ts +++ b/client/src/utils/index.ts @@ -3,6 +3,7 @@ import type { UIActionResult } from '@mcp-ui/client'; import { TAskFunction } from '~/common'; import logger from './logger'; +export * from './errors'; export * from './map'; export * from './json'; export * from './files';