Skip to content

Commit 5041880

Browse files
authored
fix: handle Qdrant deletion errors gracefully to prevent indexing interruption (#6296)
1 parent 1da82b2 commit 5041880

File tree

3 files changed

+78
-25
lines changed

3 files changed

+78
-25
lines changed

src/services/code-index/processors/file-watcher.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,15 +204,20 @@ export class FileWatcher implements IFileWatcher {
204204
currentFile: path,
205205
})
206206
}
207-
} catch (error) {
208-
overallBatchError = error as Error
207+
} catch (error: any) {
208+
const errorStatus = error?.status || error?.response?.status || error?.statusCode
209+
const errorMessage = error instanceof Error ? error.message : String(error)
210+
209211
// Log telemetry for deletion error
210212
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
211-
error: sanitizeErrorMessage(overallBatchError.message),
213+
error: sanitizeErrorMessage(errorMessage),
212214
location: "deletePointsByMultipleFilePaths",
213215
errorType: "deletion_error",
216+
errorStatus: errorStatus,
214217
})
215218

219+
// Mark all paths as error
220+
overallBatchError = error as Error
216221
for (const path of pathsToExplicitlyDelete) {
217222
batchResults.push({ path, status: "error", error: error as Error })
218223
processedCountInBatch++

src/services/code-index/processors/scanner.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -281,17 +281,24 @@ export class DirectoryScanner implements IDirectoryScanner {
281281
try {
282282
await this.qdrantClient.deletePointsByFilePath(cachedFilePath)
283283
await this.cacheManager.deleteHash(cachedFilePath)
284-
} catch (error) {
284+
} catch (error: any) {
285+
const errorStatus = error?.status || error?.response?.status || error?.statusCode
286+
const errorMessage = error instanceof Error ? error.message : String(error)
287+
285288
console.error(
286289
`[DirectoryScanner] Failed to delete points for ${cachedFilePath} in workspace ${scanWorkspace}:`,
287290
error,
288291
)
292+
289293
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
290-
error: sanitizeErrorMessage(error instanceof Error ? error.message : String(error)),
294+
error: sanitizeErrorMessage(errorMessage),
291295
stack: error instanceof Error ? sanitizeErrorMessage(error.stack || "") : undefined,
292296
location: "scanDirectory:deleteRemovedFiles",
297+
errorStatus: errorStatus,
293298
})
299+
294300
if (onError) {
301+
// Report error to error handler
295302
onError(
296303
error instanceof Error
297304
? new Error(
@@ -304,7 +311,8 @@ export class DirectoryScanner implements IDirectoryScanner {
304311
),
305312
)
306313
}
307-
// Decide if we should re-throw or just log
314+
// Log error and continue processing instead of re-throwing
315+
console.error(`Failed to delete points for removed file: ${cachedFilePath}`, error)
308316
}
309317
}
310318
}
@@ -347,25 +355,30 @@ export class DirectoryScanner implements IDirectoryScanner {
347355
if (uniqueFilePaths.length > 0) {
348356
try {
349357
await this.qdrantClient.deletePointsByMultipleFilePaths(uniqueFilePaths)
350-
} catch (deleteError) {
358+
} catch (deleteError: any) {
359+
const errorStatus =
360+
deleteError?.status || deleteError?.response?.status || deleteError?.statusCode
361+
const errorMessage = deleteError instanceof Error ? deleteError.message : String(deleteError)
362+
351363
console.error(
352364
`[DirectoryScanner] Failed to delete points for ${uniqueFilePaths.length} files before upsert in workspace ${scanWorkspace}:`,
353365
deleteError,
354366
)
367+
355368
TelemetryService.instance.captureEvent(TelemetryEventName.CODE_INDEX_ERROR, {
356-
error: sanitizeErrorMessage(
357-
deleteError instanceof Error ? deleteError.message : String(deleteError),
358-
),
369+
error: sanitizeErrorMessage(errorMessage),
359370
stack:
360371
deleteError instanceof Error
361372
? sanitizeErrorMessage(deleteError.stack || "")
362373
: undefined,
363374
location: "processBatch:deletePointsByMultipleFilePaths",
364375
fileCount: uniqueFilePaths.length,
376+
errorStatus: errorStatus,
365377
})
366-
// Re-throw the error with workspace context
378+
379+
// Re-throw with workspace context
367380
throw new Error(
368-
`Failed to delete points for ${uniqueFilePaths.length} files. Workspace: ${scanWorkspace}. ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`,
381+
`Failed to delete points for ${uniqueFilePaths.length} files. Workspace: ${scanWorkspace}. ${errorMessage}`,
369382
{ cause: deleteError },
370383
)
371384
}

src/services/code-index/vector-store/qdrant-client.ts

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -423,27 +423,62 @@ export class QdrantVectorStore implements IVectorStore {
423423
}
424424

425425
try {
426+
// First check if the collection exists
427+
const collectionExists = await this.collectionExists()
428+
if (!collectionExists) {
429+
console.warn(
430+
`[QdrantVectorStore] Skipping deletion - collection "${this.collectionName}" does not exist`,
431+
)
432+
return
433+
}
434+
426435
const workspaceRoot = getWorkspacePath()
427-
const normalizedPaths = filePaths.map((filePath) => {
428-
const absolutePath = path.resolve(workspaceRoot, filePath)
429-
return path.normalize(absolutePath)
436+
437+
// Build filters using pathSegments to match the indexed fields
438+
const filters = filePaths.map((filePath) => {
439+
// IMPORTANT: Use the relative path to match what's stored in upsertPoints
440+
// upsertPoints stores the relative filePath, not the absolute path
441+
const relativePath = path.isAbsolute(filePath) ? path.relative(workspaceRoot, filePath) : filePath
442+
443+
// Normalize the relative path
444+
const normalizedRelativePath = path.normalize(relativePath)
445+
446+
// Split the path into segments like we do in upsertPoints
447+
const segments = normalizedRelativePath.split(path.sep).filter(Boolean)
448+
449+
// Create a filter that matches all segments of the path
450+
// This ensures we only delete points that match the exact file path
451+
const mustConditions = segments.map((segment, index) => ({
452+
key: `pathSegments.${index}`,
453+
match: { value: segment },
454+
}))
455+
456+
return { must: mustConditions }
430457
})
431458

432-
const filter = {
433-
should: normalizedPaths.map((normalizedPath) => ({
434-
key: "filePath",
435-
match: {
436-
value: normalizedPath,
437-
},
438-
})),
439-
}
459+
// Use 'should' to match any of the file paths (OR condition)
460+
const filter = filters.length === 1 ? filters[0] : { should: filters }
440461

441462
await this.client.delete(this.collectionName, {
442463
filter,
443464
wait: true,
444465
})
445-
} catch (error) {
446-
console.error("Failed to delete points by file paths:", error)
466+
} catch (error: any) {
467+
// Extract more detailed error information
468+
const errorMessage = error?.message || String(error)
469+
const errorStatus = error?.status || error?.response?.status || error?.statusCode
470+
const errorDetails = error?.response?.data || error?.data || ""
471+
472+
console.error(`[QdrantVectorStore] Failed to delete points by file paths:`, {
473+
error: errorMessage,
474+
status: errorStatus,
475+
details: errorDetails,
476+
collection: this.collectionName,
477+
fileCount: filePaths.length,
478+
// Include first few file paths for debugging (avoid logging too many)
479+
samplePaths: filePaths.slice(0, 3),
480+
})
481+
447482
throw error
448483
}
449484
}

0 commit comments

Comments
 (0)