{"record":{"id":"9be7fdfdce802124","repo":"spring-projects/spring-ai","slug":"failed-to-search-index","errorCode":null,"errorMessage":"Failed to search index","messagePattern":"Failed to search index","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"spring-ai-tool-search-tool/src/main/java/org/springframework/ai/tool/toolsearch/index/lucene/LuceneToolIndex.java","lineNumber":253,"sourceCode":"\t * @param queryString the search query\n\t * @param maxResults maximum number of results to return\n\t * @param minScore minimum score threshold for results\n\t * @return list of matching documents\n\t */\n\tprivate ToolSearchResponse doSearch(SessionIndex sessionIndex, String queryString, int maxResults, float minScore) {\n\t\ttry {\n\t\t\tIndexSearcher searcher = new IndexSearcher(sessionIndex.ensureAndGetReader());\n\n\t\t\tQuery query = buildQuery(queryString);\n\t\t\tif (query == null) {\n\t\t\t\treturn ToolSearchResponse.builder().build();\n\t\t\t}\n\n\t\t\tTopDocs results = searcher.search(query, maxResults);\n\t\t\treturn this.extractToolReferences(queryString, searcher, results, minScore);\n\t\t}\n\t\tcatch (IOException e) {\n\t\t\tthrow new RuntimeException(\"Failed to search index\", e);\n\t\t}\n\t}\n\n\tprivate ToolSearchResponse extractToolReferences(String query, IndexSearcher searcher, TopDocs results,\n\t\t\tfloat minScore) throws IOException {\n\n\t\tList<ToolReference> foundToolReferences = new ArrayList<>(results.scoreDocs.length);\n\t\tStoredFields storedFields = searcher.storedFields();\n\t\tfor (ScoreDoc scoreDoc : results.scoreDocs) {\n\t\t\tif (logger.isInfoEnabled()) {\n\t\t\t\tlogger.info(\"Score: \" + scoreDoc.score + \", name: \"\n\t\t\t\t\t\t+ storedFields.document(scoreDoc.doc).get(FIELD_TOOL_NAME));\n\t\t\t}\n\t\t\tif (scoreDoc.score >= minScore) {\n\t\t\t\tvar doc = storedFields.document(scoreDoc.doc);\n\t\t\t\tfoundToolReferences.add(ToolReference.builder()\n\t\t\t\t\t.relevanceScore(scoreDoc.score)\n\t\t\t\t\t.toolName(doc.get(FIELD_TOOL_NAME))","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-tool-search-tool/src/main/java/org/springframework/ai/tool/toolsearch/index/lucene/LuceneToolIndex.java#L235-L271","documentation":"Thrown by LuceneToolIndex.doSearch() (called from search()) when the Lucene search pipeline fails with an IOException: opening/reusing the IndexReader (ensureAndGetReader), executing searcher.search(), or reading stored fields in extractToolReferences. The library wraps it as a RuntimeException(\"Failed to search index\") because IOException is checked but the search API is not declared to throw.","triggerScenarios":"Calling search() when the session's index directory is unreadable or deleted, the IndexReader was closed by a concurrent clearIndex(), the underlying index files are corrupt, or an I/O error occurs while fetching stored documents for the matched score docs.","commonSituations":"Index directory removed or remounted read-only between add and search; reader closed by concurrent clearIndex()/close(); corrupted segment files after a crash mid-commit; NFS/network storage dropping the index directory; searching after the index was rebuilt with an incompatible Lucene version.","solutions":["Inspect the wrapped IOException cause to see which step failed (reader open, search, stored-fields fetch)","Verify the index directory exists and is readable/writable by the process","Ensure no clearIndex() or close() runs concurrently with search() for the same session","If reader/index is stale or corrupt, re-initialize the session (re-add documents) and search again","Catch the RuntimeException and return an empty ToolSearchResponse as a graceful fallback in the advisor flow"],"exampleFix":"// before\nToolSearchResponse resp = index.search(sessionId, \"sql query\", 5, 0.5f); // throws RuntimeException on IO failure\n// after\nToolSearchResponse resp;\ntry {\n    resp = index.search(sessionId, \"sql query\", 5, 0.5f);\n}\ncatch (RuntimeException e) {\n    logger.warn(\"tool search failed, returning empty result\", e);\n    resp = ToolSearchResponse.builder().toolReferences(List.of()).totalMatches(0).build();\n}","handlingStrategy":"fallback","validationCode":"File idxDir = new File(indexPath);\nif (!idxDir.isDirectory() || !Files.isReadable(idxDir.toPath())) {\n    throw new IllegalStateException(\"Index directory missing or unreadable: \" + indexPath);\n}","typeGuard":null,"tryCatchPattern":"ToolSearchResponse resp;\ntry {\n    resp = index.search(sessionId, query, maxResults, minScore);\n}\ncatch (RuntimeException e) {\n    if (e.getCause() instanceof java.io.IOException) {\n        logger.warn(\"Lucene search IO failure for session \" + sessionId, e);\n    }\n    resp = ToolSearchResponse.builder().toolReferences(List.of()).totalMatches(0).build();\n}","preventionTips":["Ensure no clearIndex()/close() runs concurrently with search() on the same session","Keep the index directory on stable, readable local storage","Rebuild the index on startup if segment files are detected as corrupt","Return an empty ToolSearchResponse as graceful degradation so the advisor flow continues"],"tags":["lucene","io","search","index"],"backgroundTag":"file-read-failed","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}