diff --git a/src/services/code-index/processors/file-watcher.ts b/src/services/code-index/processors/file-watcher.ts index 6dc1cd1835..c59d471449 100644 --- a/src/services/code-index/processors/file-watcher.ts +++ b/src/services/code-index/processors/file-watcher.ts @@ -204,15 +204,20 @@ export class FileWatcher implements IFileWatcher { currentFile: path, }) } - } catch (error) { - overallBatchError = error as Error + } catch (error: any) { + const errorStatus = error?.status || error?.response?.status || error?.statusCode + const errorMessage = error instanceof Error ? error.message : String(error) + // Log telemetry for deletion error TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, { - error: sanitizeErrorMessage(overallBatchError.message), + error: sanitizeErrorMessage(errorMessage), location: "deletePointsByMultipleFilePaths", errorType: "deletion_error", + errorStatus: errorStatus, }) + // Mark all paths as error + overallBatchError = error as Error for (const path of pathsToExplicitlyDelete) { batchResults.push({ path, status: "error", error: error as Error }) processedCountInBatch++ diff --git a/src/services/code-index/processors/scanner.ts b/src/services/code-index/processors/scanner.ts index 3203076d12..27362b8b74 100644 --- a/src/services/code-index/processors/scanner.ts +++ b/src/services/code-index/processors/scanner.ts @@ -281,17 +281,24 @@ export class DirectoryScanner implements IDirectoryScanner { try { await this.qdrantClient.deletePointsByFilePath(cachedFilePath) await this.cacheManager.deleteHash(cachedFilePath) - } catch (error) { + } catch (error: any) { + const errorStatus = error?.status || error?.response?.status || error?.statusCode + const errorMessage = error instanceof Error ? error.message : String(error) + console.error( `[DirectoryScanner] Failed to delete points for ${cachedFilePath} in workspace ${scanWorkspace}:`, error, ) + TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, { - error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)), + error: sanitizeErrorMessage(errorMessage), stack: error instanceof Error ? sanitizeErrorMessage(error.stack || "") : undefined, location: "scanDirectory:deleteRemovedFiles", + errorStatus: errorStatus, }) + if (onError) { + // Report error to error handler onError( error instanceof Error ? new Error( @@ -304,7 +311,8 @@ export class DirectoryScanner implements IDirectoryScanner { ), ) } - // Decide if we should re-throw or just log + // Log error and continue processing instead of re-throwing + console.error(`Failed to delete points for removed file: ${cachedFilePath}`, error) } } } @@ -347,25 +355,30 @@ export class DirectoryScanner implements IDirectoryScanner { if (uniqueFilePaths.length > 0) { try { await this.qdrantClient.deletePointsByMultipleFilePaths(uniqueFilePaths) - } catch (deleteError) { + } catch (deleteError: any) { + const errorStatus = + deleteError?.status || deleteError?.response?.status || deleteError?.statusCode + const errorMessage = deleteError instanceof Error ? deleteError.message : String(deleteError) + console.error( `[DirectoryScanner] Failed to delete points for ${uniqueFilePaths.length} files before upsert in workspace ${scanWorkspace}:`, deleteError, ) + TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, { - error: sanitizeErrorMessage( - deleteError instanceof Error ? deleteError.message : String(deleteError), - ), + error: sanitizeErrorMessage(errorMessage), stack: deleteError instanceof Error ? sanitizeErrorMessage(deleteError.stack || "") : undefined, location: "processBatch:deletePointsByMultipleFilePaths", fileCount: uniqueFilePaths.length, + errorStatus: errorStatus, }) - // Re-throw the error with workspace context + + // Re-throw with workspace context throw new Error( - `Failed to delete points for ${uniqueFilePaths.length} files. Workspace: ${scanWorkspace}. ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`, + `Failed to delete points for ${uniqueFilePaths.length} files. Workspace: ${scanWorkspace}. ${errorMessage}`, { cause: deleteError }, ) } diff --git a/src/services/code-index/vector-store/qdrant-client.ts b/src/services/code-index/vector-store/qdrant-client.ts index 5121d65b97..0218e37295 100644 --- a/src/services/code-index/vector-store/qdrant-client.ts +++ b/src/services/code-index/vector-store/qdrant-client.ts @@ -423,27 +423,62 @@ export class QdrantVectorStore implements IVectorStore { } try { + // First check if the collection exists + const collectionExists = await this.collectionExists() + if (!collectionExists) { + console.warn( + `[QdrantVectorStore] Skipping deletion - collection "${this.collectionName}" does not exist`, + ) + return + } + const workspaceRoot = getWorkspacePath() - const normalizedPaths = filePaths.map((filePath) => { - const absolutePath = path.resolve(workspaceRoot, filePath) - return path.normalize(absolutePath) + + // Build filters using pathSegments to match the indexed fields + const filters = filePaths.map((filePath) => { + // IMPORTANT: Use the relative path to match what's stored in upsertPoints + // upsertPoints stores the relative filePath, not the absolute path + const relativePath = path.isAbsolute(filePath) ? path.relative(workspaceRoot, filePath) : filePath + + // Normalize the relative path + const normalizedRelativePath = path.normalize(relativePath) + + // Split the path into segments like we do in upsertPoints + const segments = normalizedRelativePath.split(path.sep).filter(Boolean) + + // Create a filter that matches all segments of the path + // This ensures we only delete points that match the exact file path + const mustConditions = segments.map((segment, index) => ({ + key: `pathSegments.${index}`, + match: { value: segment }, + })) + + return { must: mustConditions } }) - const filter = { - should: normalizedPaths.map((normalizedPath) => ({ - key: "filePath", - match: { - value: normalizedPath, - }, - })), - } + // Use 'should' to match any of the file paths (OR condition) + const filter = filters.length === 1 ? filters[0] : { should: filters } await this.client.delete(this.collectionName, { filter, wait: true, }) - } catch (error) { - console.error("Failed to delete points by file paths:", error) + } catch (error: any) { + // Extract more detailed error information + const errorMessage = error?.message || String(error) + const errorStatus = error?.status || error?.response?.status || error?.statusCode + const errorDetails = error?.response?.data || error?.data || "" + + console.error(`[QdrantVectorStore] Failed to delete points by file paths:`, { + error: errorMessage, + status: errorStatus, + details: errorDetails, + collection: this.collectionName, + fileCount: filePaths.length, + // Include first few file paths for debugging (avoid logging too many) + samplePaths: filePaths.slice(0, 3), + }) + throw error } }