🧭 fix: Robust 404 Conversation Not Found Redirect (#11853)

* 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 <daniel@mightyacorn.com>
This commit is contained in:
Danny Avila 2026-02-18 11:41:53 -05:00 committed by GitHub
parent 252a5cc7ca
commit 42718faad2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 66 additions and 4 deletions

View file

@ -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<TPreset[]>,
@ -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,
},
);

View file

@ -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",

View file

@ -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,

View file

@ -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;

View file

@ -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';