🧭 fix: Robust 404 Conversation Not Found Redirect#11853
Conversation
- 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
There was a problem hiding this comment.
Pull request overview
Adds a shared 404-detection utility and uses it to improve UX and query behavior when a requested conversation ID no longer exists.
Changes:
- Introduces
getResponseStatus/isNotFoundErrorutilities and re-exports them via~/utils. - Updates conversation-by-id query to stop retrying on HTTP 404.
- Updates
ChatRouteto toast + create a new conversation when the initial conversation fetch returns 404, and adds an English translation string.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| client/src/utils/index.ts | Re-exports new error helpers from the utils barrel. |
| client/src/utils/errors.ts | Adds HTTP-status extraction helpers for unknown error shapes. |
| client/src/data-provider/queries.ts | Disables react-query retries for 404 when fetching a conversation by id. |
| client/src/routes/ChatRoute.tsx | On 404, shows a warning toast and starts a new conversation flow. |
| client/src/locales/en/translation.json | Adds English string for “Conversation not found”. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * 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) { | ||
| return (error as { status: number }).status; | ||
| } |
There was a problem hiding this comment.
getResponseStatus's fallback path only checks a top-level status property and assumes it's a number. In this codebase, many non-axios checks look at error.response?.status; as written, those errors will return undefined even when an HTTP status is present, and a non-numeric status value would be incorrectly cast. Consider extending the fallback to also handle { response: { status } } and guarding typeof status === 'number' before returning it (and update the docstring accordingly).
| * 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) { | |
| return (error as { status: number }).status; | |
| } | |
| * HTTP client used. Handles Axios errors first, then falls back to checking | |
| * for a numeric `status` property on the error itself or on `error.response`, | |
| * 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') { | |
| // First, check for a top-level numeric `status` property. | |
| const topLevelStatus = (error as { status?: unknown }).status; | |
| if (typeof topLevelStatus === 'number') { | |
| return topLevelStatus; | |
| } | |
| // Then, check for a nested `response.status` pattern used by some clients. | |
| if ('response' in error) { | |
| const response = (error as { response?: { status?: unknown } }).response; | |
| const nestedStatus = response?.status; | |
| if (typeof nestedStatus === 'number') { | |
| return nestedStatus; | |
| } | |
| } | |
| } |
client/src/routes/ChatRoute.tsx
Outdated
| } else if (initialConvoQuery.isError && isNotFoundError(initialConvoQuery.error)) { | ||
| showToast({ | ||
| message: localize('com_ui_conversation_not_found'), | ||
| severity: NotificationSeverity.WARNING, | ||
| }); | ||
| newConversation(); |
There was a problem hiding this comment.
In the 404 branch, newConversation() is called without waiting for endpointsQuery.data/modelsQuery.data (unlike the other branches), but useNewConvo may attempt to build a default conversation using endpoints/models data that may not be loaded yet. If endpoints config isn't available yet, this can result in a conversation with endpoint becoming undefined (see buildDefaultConvo's early return when endpoint is falsy) and hasSetConversation.current being set to true, which would prevent later initialization once queries complete. Also, since the route uses c/:conversationId?, navigating to /c yields an empty conversationId which will likely 404 and trigger this toast/redirect even though it's not really a missing conversation. Consider guarding this branch with conversationId truthiness and gating on endpointsQuery.data && modelsQuery.data (or passing modelsData: modelsQuery.data) before setting hasSetConversation.current.
| } else if (initialConvoQuery.isError && isNotFoundError(initialConvoQuery.error)) { | |
| showToast({ | |
| message: localize('com_ui_conversation_not_found'), | |
| severity: NotificationSeverity.WARNING, | |
| }); | |
| newConversation(); | |
| } else if ( | |
| initialConvoQuery.isError && | |
| isNotFoundError(initialConvoQuery.error) && | |
| conversationId && | |
| endpointsQuery.data && | |
| modelsQuery.data | |
| ) { | |
| showToast({ | |
| message: localize('com_ui_conversation_not_found'), | |
| severity: NotificationSeverity.WARNING, | |
| }); | |
| newConversation({ | |
| modelsData: modelsQuery.data, | |
| }); |
- 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.
- 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.
- Added logging for the initial conversation query error when a conversation is not found, improving debugging capabilities and error tracking in the ChatRoute component.
* 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 <[email protected]>
Summary
I added robust handling for when a user navigates to a conversation URL that no longer exists, replacing silent retry loops with an immediate redirect and a user-facing toast notification.
client/src/utils/errors.tsexportinggetResponseStatus, which extracts an HTTP status code from either an Axios error or a plain object with astatusproperty, andisNotFoundError, which returnstruewhen that status is404; re-exported both throughclient/src/utils/index.ts.retryfunction touseGetConvoIdQuerythat immediately short-circuits withfalseon 404 errors, eliminating the exponential-backoff retry delay that previously stalled error-state surfacing.ChatRouteinitialization effect that fires wheninitialConvoQuery.isErroris true andisNotFoundErrorconfirms a 404, displays aWARNINGseverity toast with the newcom_ui_conversation_not_foundlocale key, and callsnewConversation()to redirect to a fresh conversation.initialConvoQuery.isErrorto the effect's dependency array so the effect correctly re-runs when the query transitions into an error state."com_ui_conversation_not_found": "Conversation not found"key to the English locale file.Change Type
Testing
To test, navigate directly to a conversation URL with a deleted or otherwise non-existent conversation ID (e.g.,
/c/some-invalid-id). Confirm:/c/new).Test Configuration
Checklist