Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/services/code-index/processors/file-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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++
Expand Down
31 changes: 22 additions & 9 deletions src/services/code-index/processors/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -304,7 +311,8 @@ export class DirectoryScanner implements IDirectoryScanner {
),
)
}
// Decide if we should re-throw or just log
// Re-throw to maintain consistent error handling
throw error
}
}
}
Expand Down Expand Up @@ -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 },
)
}
Expand Down
78 changes: 64 additions & 14 deletions src/services/code-index/vector-store/qdrant-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,15 @@ export class QdrantVectorStore implements IVectorStore {
* @param filePath Path of the file to delete points for
*/
async deletePointsByFilePath(filePath: string): Promise<void> {
return this.deletePointsByMultipleFilePaths([filePath])
try {
return await this.deletePointsByMultipleFilePaths([filePath])
} catch (error) {
// Error is already handled in deletePointsByMultipleFilePaths
// This is just for consistency in case the method is called directly
console.error(`[QdrantVectorStore] Error in deletePointsByFilePath for ${filePath}:`, error)
// Re-throw to maintain the interface contract
throw error
}
}

async deletePointsByMultipleFilePaths(filePaths: string[]): Promise<void> {
Expand All @@ -423,27 +431,69 @@ 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,
},
})),
}
// Log the paths being deleted for debugging
console.log(
`[QdrantVectorStore] Attempting to delete points for ${filePaths.length} file(s) from collection "${this.collectionName}"`,
)

// 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)

console.log(`[QdrantVectorStore] Successfully deleted points for ${filePaths.length} file(s)`)
} 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
}
}
Expand Down