feat: Enhance CodexLens uninstallation process with improved error handling and process termination for locked files

This commit is contained in:
catlog22
2026-01-05 22:40:26 +08:00
parent 853977c676
commit f90c6b9fab
3 changed files with 94 additions and 3 deletions

View File

@@ -407,6 +407,41 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
if (pathname === '/api/codexlens/uninstall' && req.method === 'POST') {
handlePostRequest(req, res, async () => {
try {
// Stop watcher if running (to release file handles)
if (watcherStats.running && watcherProcess) {
console.log('[CodexLens] Stopping watcher before uninstall...');
try {
watcherProcess.kill('SIGTERM');
await new Promise(resolve => setTimeout(resolve, 500));
if (watcherProcess && !watcherProcess.killed) {
watcherProcess.kill('SIGKILL');
}
} catch {
// Ignore errors stopping watcher
}
watcherStats.running = false;
watcherProcess = null;
}
// Cancel any running indexing process
if (currentIndexingProcess) {
console.log('[CodexLens] Cancelling indexing before uninstall...');
try {
if (process.platform === 'win32') {
const { execSync } = require('child_process');
execSync(`taskkill /pid ${currentIndexingProcess.pid} /T /F`, { stdio: 'ignore' });
} else {
currentIndexingProcess.kill('SIGKILL');
}
} catch {
// Ignore errors
}
currentIndexingProcess = null;
}
// Wait a moment for processes to fully exit and release handles
await new Promise(resolve => setTimeout(resolve, 1000));
const result = await uninstallCodexLens();
if (result.success) {
// Broadcast uninstallation event

View File

@@ -1360,11 +1360,57 @@ async function uninstallCodexLens(): Promise<BootstrapResult> {
}
console.log('[CodexLens] Uninstalling CodexLens...');
// On Windows, kill any Python processes that might be holding locks on .db files
if (process.platform === 'win32') {
console.log('[CodexLens] Killing any CodexLens Python processes...');
const { execSync } = await import('child_process');
try {
// Kill any python processes from our venv that might be holding file locks
execSync(`taskkill /F /IM python.exe /FI "MODULES eq sqlite3" 2>nul`, { stdio: 'ignore' });
} catch {
// Ignore errors - no processes to kill
}
// Small delay to allow file handles to be released
await new Promise(resolve => setTimeout(resolve, 500));
}
console.log(`[CodexLens] Removing directory: ${CODEXLENS_DATA_DIR}`);
// Remove the entire .codexlens directory
// Remove the entire .codexlens directory with retry logic for locked files
const fs = await import('fs');
fs.rmSync(CODEXLENS_DATA_DIR, { recursive: true, force: true });
const path = await import('path');
// Helper function to remove directory with retries (Windows EBUSY workaround)
const removeWithRetry = async (dirPath: string, maxRetries = 3, delay = 1000): Promise<void> => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
fs.rmSync(dirPath, { recursive: true, force: true, maxRetries: 3, retryDelay: 500 });
return;
} catch (err: any) {
if (err.code === 'EBUSY' || err.code === 'EPERM' || err.code === 'ENOTEMPTY') {
console.log(`[CodexLens] Retry ${attempt}/${maxRetries} - file locked, waiting...`);
if (attempt < maxRetries) {
// On Windows, try to forcefully release file handles
if (process.platform === 'win32' && err.path) {
try {
const { execSync } = await import('child_process');
// Try to close handles on the specific file
execSync(`handle -c ${err.path} -y 2>nul`, { stdio: 'ignore' });
} catch {
// handle.exe may not be installed, ignore
}
}
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
throw err;
}
}
};
await removeWithRetry(CODEXLENS_DATA_DIR);
// Reset bootstrap cache
bootstrapChecked = false;
@@ -1374,7 +1420,15 @@ async function uninstallCodexLens(): Promise<BootstrapResult> {
console.log('[CodexLens] CodexLens uninstalled successfully');
return { success: true, message: 'CodexLens uninstalled successfully' };
} catch (err) {
return { success: false, error: `Failed to uninstall CodexLens: ${(err as Error).message}` };
const errorMsg = (err as Error).message;
// Provide helpful message for Windows users with locked files
if (errorMsg.includes('EBUSY') || errorMsg.includes('resource busy')) {
return {
success: false,
error: `Failed to uninstall CodexLens: Files are locked. Please close any applications using CodexLens indexes (e.g., Claude Code, VS Code) and try again. Details: ${errorMsg}`
};
}
return { success: false, error: `Failed to uninstall CodexLens: ${errorMsg}` };
}
}

View File

@@ -24,6 +24,7 @@ src/codexlens/parsers/factory.py
src/codexlens/parsers/tokenizer.py
src/codexlens/parsers/treesitter_parser.py
src/codexlens/search/__init__.py
src/codexlens/search/binary_searcher.py
src/codexlens/search/chain_search.py
src/codexlens/search/enrichment.py
src/codexlens/search/graph_expander.py
@@ -46,6 +47,7 @@ src/codexlens/semantic/reranker/__init__.py
src/codexlens/semantic/reranker/api_reranker.py
src/codexlens/semantic/reranker/base.py
src/codexlens/semantic/reranker/factory.py
src/codexlens/semantic/reranker/fastembed_reranker.py
src/codexlens/semantic/reranker/legacy.py
src/codexlens/semantic/reranker/litellm_reranker.py
src/codexlens/semantic/reranker/onnx_reranker.py