{"record":{"id":"a6ac9635b5f2310e","repo":"infiniflow/ragflow","slug":"failed-to-delete-search","errorCode":null,"errorMessage":"Failed to delete search","messagePattern":"Failed to delete search","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/pages/next-searches/hooks.ts","lineNumber":272,"sourceCode":"    },\n  });\n\n  return { data: data?.data, isLoading, isError };\n};\n\nexport const useDeleteSearch = () => {\n  const { t } = useTranslation();\n  const queryClient = useQueryClient();\n  const {\n    data,\n    isError,\n    mutateAsync: deleteSearchMutation,\n  } = useMutation<DeleteSearchResponse, Error, DeleteSearchProps>({\n    mutationKey: ['deleteSearch'],\n    mutationFn: async (props) => {\n      const { data: response } = await searchService.deleteSearch(props);\n      if (response.code !== 0) {\n        throw new Error(response.message || 'Failed to delete search');\n      }\n\n      queryClient.invalidateQueries({ queryKey: ['searchList'] });\n      return response;\n    },\n    onSuccess: () => {\n      message.success(t('message.deleted'));\n    },\n    onError: (error) => {\n      message.error(t('message.error', { error: error.message }));\n    },\n  });\n\n  const deleteSearch = useCallback(\n    (props: DeleteSearchProps) => {\n      return deleteSearchMutation(props);\n    },\n    [deleteSearchMutation],","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/web/src/pages/next-searches/hooks.ts#L254-L290","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Log the full response (code + message) from searchService.deleteSearch before throwing to surface the real backend reason","Verify the search_id still exists in the ['searchList'] cache before invoking the mutation","Confirm authentication is valid (token/cookie) by re-issuing the request after login refresh","Check backend logs for the corresponding DELETE search request to see the server-side error","If the search was already deleted (404-style code), treat it as success and invalidate ['searchList'] anyway"],"exampleFix":"// before\nif (response.code !== 0) {\n  throw new Error(response.message || 'Failed to delete search');\n}\n\n// after\nif (response.code !== 0) {\n  const alreadyGone = /not exist|not found/i.test(response.message || '');\n  if (alreadyGone) {\n    queryClient.invalidateQueries({ queryKey: ['searchList'] });\n    return response;\n  }\n  throw new Error(response.message || 'Failed to delete search');\n}","handlingStrategy":"try-catch","validationCode":"const existsInCache = (queryClient, searchId) =>\n  queryClient\n    .getQueryData(['searchList'])\n    ?.pages?.flat?.()?.some?.((s) => s.id === searchId) ?? true;","typeGuard":"const isApiEnvelope = (v: any): v is { code: number; message?: string; data?: unknown } =>\n  typeof v === 'object' && v !== null && typeof v.code === 'number';","tryCatchPattern":"try {\n  await deleteSearchMutation({ search_id: id });\n} catch (e) {\n  if (/not (exist|found)/i.test(e.message)) {\n    queryClient.invalidateQueries({ queryKey: ['searchList'] });\n    return;\n  }\n  message.error(e.message);\n}","preventionTips":["Invalidate ['searchList'] on mount so stale ids are pruned before delete attempts","Centralize code!==0 handling in a response interceptor so message is never lost"],"tags":["react-query","api-contract","search","mutation"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}