infiniflow/ragflow · error · Error

Failed to delete search

Error message

Failed to delete search

What it means

Thrown by the useDeleteSearch React Query mutation in web/src/pages/next-searches/hooks.ts:272 when the backend DELETE-search API responds with a non-zero business code. The service layer (searchService.deleteSearch) resolves the HTTP call successfully, but RAGFlow's API convention returns code !== 0 to signal a server-side rejection (missing search, permission denied, DB error). The hook converts that into a generic Error whose message falls back to 'Failed to delete search' when the server message is empty.

Source

Thrown at web/src/pages/next-searches/hooks.ts:272

    },
  });

  return { data: data?.data, isLoading, isError };
};

export const useDeleteSearch = () => {
  const { t } = useTranslation();
  const queryClient = useQueryClient();
  const {
    data,
    isError,
    mutateAsync: deleteSearchMutation,
  } = useMutation<DeleteSearchResponse, Error, DeleteSearchProps>({
    mutationKey: ['deleteSearch'],
    mutationFn: async (props) => {
      const { data: response } = await searchService.deleteSearch(props);
      if (response.code !== 0) {
        throw new Error(response.message || 'Failed to delete search');
      }

      queryClient.invalidateQueries({ queryKey: ['searchList'] });
      return response;
    },
    onSuccess: () => {
      message.success(t('message.deleted'));
    },
    onError: (error) => {
      message.error(t('message.error', { error: error.message }));
    },
  });

  const deleteSearch = useCallback(
    (props: DeleteSearchProps) => {
      return deleteSearchMutation(props);
    },
    [deleteSearchMutation],

View on GitHub (pinned to 554fb1133a)

Solutions

  1. Log the full response (code + message) from searchService.deleteSearch before throwing to surface the real backend reason
  2. Verify the search_id still exists in the ['searchList'] cache before invoking the mutation
  3. Confirm authentication is valid (token/cookie) by re-issuing the request after login refresh
  4. Check backend logs for the corresponding DELETE search request to see the server-side error
  5. If the search was already deleted (404-style code), treat it as success and invalidate ['searchList'] anyway

Example fix

// before
if (response.code !== 0) {
  throw new Error(response.message || 'Failed to delete search');
}

// after
if (response.code !== 0) {
  const alreadyGone = /not exist|not found/i.test(response.message || '');
  if (alreadyGone) {
    queryClient.invalidateQueries({ queryKey: ['searchList'] });
    return response;
  }
  throw new Error(response.message || 'Failed to delete search');
}
Defensive patterns

Strategy: try-catch

Validate before calling

const existsInCache = (queryClient, searchId) =>
  queryClient
    .getQueryData(['searchList'])
    ?.pages?.flat?.()?.some?.((s) => s.id === searchId) ?? true;

Type guard

const isApiEnvelope = (v: any): v is { code: number; message?: string; data?: unknown } =>
  typeof v === 'object' && v !== null && typeof v.code === 'number';

Try / catch

try {
  await deleteSearchMutation({ search_id: id });
} catch (e) {
  if (/not (exist|found)/i.test(e.message)) {
    queryClient.invalidateQueries({ queryKey: ['searchList'] });
    return;
  }
  message.error(e.message);
}

Prevention

When it happens

Trigger: Calling the deleteSearch mutation with a search_id that no longer exists; the API session/token expired so the backend returns an error code; a proxy or gateway returns a 200-wrapped error payload with code != 0; the response body lacks a message field so the fallback string is used.

Common situations: Stale UI list: user deletes a search that another tab/session already removed. Logged-out token after session timeout. Backend API version change where the delete endpoint moved or was renamed. Multi-tenant permission mismatch (search owned by another user).

Related errors


AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15). Data as JSON: /api/errors/a6ac9635b5f2310e. Report an issue: GitHub.