Skip to content

Conversation

@tkislan
Copy link
Member

@tkislan tkislan commented Oct 31, 2025

Signed-off-by: Tomas Kislan tomas@kislan.sk

Fixes #

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Virtual environment directories are now properly deleted from disk when removed
    • Enhanced cancellation handling during environment cleanup operations
    • Improved error handling for environment deletion processes

…ote environment

Signed-off-by: Tomas Kislan <tomas@kislan.sk>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 31, 2025

📝 Walkthrough

Walkthrough

The DeepnoteEnvironmentManager now deletes virtual environment directories from disk during environment cleanup. An IFileSystem dependency was injected into the class, and the deleteEnvironment method was extended to call fileSystem.delete() on the environment directory after existing cleanup logic, with cancellation checks and logging. Tests were updated to verify disk cleanup behavior.

Sequence Diagram

sequenceDiagram
    participant caller as Caller
    participant mgr as DeepnoteEnvironmentManager
    participant fs as FileSystem
    
    caller->>mgr: deleteEnvironment(token)
    
    note over mgr: Check cancellation
    mgr->>mgr: Cancellation.throwIfCanceled(token)
    
    note over mgr: Existing cleanup logic
    mgr->>mgr: persistence.delete()<br/>events fired
    
    note over mgr: NEW: Check cancellation again
    mgr->>mgr: Cancellation.throwIfCanceled(token)
    
    rect rgb(200, 230, 201)
        note over mgr, fs: NEW: Disk deletion
        mgr->>fs: delete(environmentDirectory)
        fs-->>mgr: success
        mgr->>mgr: log deletion result
    end
    
    note over mgr: Catch errors<br/>(log warning, don't fail)
    
    mgr-->>caller: complete
Loading

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: Delete python virtual environment directory when removing deepnote environment" directly and accurately describes the main change in the pull request. The modifications to DeepnoteEnvironmentManager add file system integration to delete the virtual environment directory when deleteEnvironment is invoked, exactly as the title states. The title is specific, uses clear language, and avoids vague terminology, making it easy for teammates to understand the primary purpose of the change from the commit history.

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Oct 31, 2025

Codecov Report

❌ Patch coverage is 93.75000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 72%. Comparing base (dee6066) to head (aa8334c).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...te/environments/deepnoteEnvironmentManager.node.ts 87% 1 Missing ⚠️
...vironments/deepnoteEnvironmentManager.unit.test.ts 95% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@          Coverage Diff          @@
##            main    #150   +/-   ##
=====================================
  Coverage     72%     72%           
=====================================
  Files        568     568           
  Lines      45226   45256   +30     
  Branches    5450    5452    +2     
=====================================
+ Hits       32827   32855   +28     
- Misses     10610   10611    +1     
- Partials    1789    1790    +1     
Files with missing lines Coverage Δ
...te/environments/deepnoteEnvironmentManager.node.ts 89% <87%> (-1%) ⬇️
...vironments/deepnoteEnvironmentManager.unit.test.ts 99% <95%> (-1%) ⬇️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dee6066 and aa8334c.

📒 Files selected for processing (2)
  • src/kernels/deepnote/environments/deepnoteEnvironmentManager.node.ts (3 hunks)
  • src/kernels/deepnote/environments/deepnoteEnvironmentManager.unit.test.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.node.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use *.node.ts for Desktop-specific implementations that require full file system access and Python environments

Files:

  • src/kernels/deepnote/environments/deepnoteEnvironmentManager.node.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Inject interfaces, not concrete classes
Avoid circular dependencies
Use l10n.t() for user-facing strings
Use typed error classes from src/platform/errors/ when throwing or handling errors
Use the ILogger service instead of console.log
Preserve error details while scrubbing PII in messages and telemetry
Include the Microsoft copyright header in source files
Prefer async/await and handle cancellation with CancellationToken

Files:

  • src/kernels/deepnote/environments/deepnoteEnvironmentManager.node.ts
  • src/kernels/deepnote/environments/deepnoteEnvironmentManager.unit.test.ts
src/kernels/**/*.ts

📄 CodeRabbit inference engine (.github/instructions/kernel.instructions.md)

src/kernels/**/*.ts: Use event-driven updates (EventEmitter) for state changes
Monitor and dispose pending promises to prevent leaks during teardown
Use WeakRef/weak references for notebook/object backreferences to avoid memory leaks
Respect CancellationToken in all async operations

Files:

  • src/kernels/deepnote/environments/deepnoteEnvironmentManager.node.ts
  • src/kernels/deepnote/environments/deepnoteEnvironmentManager.unit.test.ts
**/!(*.node|*.web).ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place shared cross-platform logic in common .ts files (not suffixed with .node or .web)

Files:

  • src/kernels/deepnote/environments/deepnoteEnvironmentManager.unit.test.ts
**/*.unit.test.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place unit tests in files matching *.unit.test.ts

Files:

  • src/kernels/deepnote/environments/deepnoteEnvironmentManager.unit.test.ts
**/*.{test,spec}.ts

📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)

In unit tests, when a mock is returned from a promise, ensure the mocked instance has an undefined then property to avoid hanging tests

Files:

  • src/kernels/deepnote/environments/deepnoteEnvironmentManager.unit.test.ts
🧬 Code graph analysis (1)
src/kernels/deepnote/environments/deepnoteEnvironmentManager.node.ts (1)
src/platform/logging/index.ts (1)
  • logger (35-48)
🔇 Additional comments (1)
src/kernels/deepnote/environments/deepnoteEnvironmentManager.node.ts (1)

35-36: IFileSystem injection added correctly.

The dependency injection follows coding guidelines by injecting the interface rather than a concrete implementation.

Comment on lines +196 to +203
// Delete the virtual environment directory from disk
try {
await this.fileSystem.delete(config.venvPath);
logger.info(`Deleted virtual environment directory: ${config.venvPath.fsPath}`);
} catch (error) {
// Log but don't fail - the directory might not exist or might already be deleted
logger.warn(`Failed to delete virtual environment directory: ${config.venvPath.fsPath}`, error);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify fileSystem.delete handles recursive deletion.

Virtual environment directories contain nested files and folders. Ensure IFileSystem.delete recursively removes directories by default, or pass explicit options if required.

Run this script to check the IFileSystem interface signature and usage patterns:


🏁 Script executed:

#!/bin/bash
# Check IFileSystem.delete signature and find usage examples

# Find the IFileSystem interface definition
echo "=== IFileSystem interface ==="
ast-grep --pattern $'interface IFileSystem {
  $$$
  delete($$$) $_
  $$$
}'

# Find other delete calls to see if options are passed
echo -e "\n=== Usage examples ==="
rg -nP -A2 'fileSystem\.delete\(' --type=ts

Length of output: 450



🏁 Script executed:

#!/bin/bash
# Find where IFileSystem is imported/defined

# Check imports in the file under review
echo "=== IFileSystem import in deepnoteEnvironmentManager ==="
rg -n 'import.*IFileSystem|from.*IFileSystem' src/kernels/deepnote/environments/deepnoteEnvironmentManager.node.ts

# Search for IFileSystem interface definition
echo -e "\n=== IFileSystem interface definition ==="
rg -nP 'interface\s+IFileSystem|export.*IFileSystem' --type=ts -A 10

# Check type definitions
echo -e "\n=== IFileSystem type/interface ==="
fd -e ts | xargs grep -l 'interface IFileSystem' 2>/dev/null | head -5

Length of output: 7386


🏁 Script executed:

#!/bin/bash
# If IFileSystem is from VS Code, check its API
echo "Checking if delete uses recursive flag..."
# Common patterns in file system APIs
echo "- VS Code FileSystem.delete() signature"
echo "- Check if recursive option exists"

Length of output: 116



🏁 Script executed:

#!/bin/bash
# Check FileSystem.delete implementation

echo "=== FileSystem delete implementation ==="
rg -nA 5 'delete\s*\(' src/platform/common/platform/fileSystem.ts --type=ts

echo -e "\n=== FileSystemBase delete implementation ==="
rg -nA 5 'delete\s*\(' src/platform/common/platform/fileSystem.ts --type=ts

echo -e "\n=== Check parent class ==="
head -30 src/platform/common/platform/fileSystem.ts

Length of output: 1573


🌐 Web query:

VS Code workspace.fs.delete API recursive option default behavior

💡 Result:

Short answer: options.recursive defaults to false — if you omit it, folder deletion is non‑recursive (you must pass { recursive: true } to delete a non‑empty directory). [1][2]

Sources:

  • VS Code API / FileSystemProvider (delete signature and options). [1]
  • vscode.d.ts showing the FileSystemProvider.delete/watch signatures. [2]

Pass { recursive: true } option to vscode FileSystem API.

VS Code's workspace.fs.delete() defaults to recursive: false. Virtual environment directories contain nested Python files and packages, so deletion at line 198 will fail silently. The catch block logs a warning but doesn't rethrow, leaving the directory on disk and causing a disk space leak.

Update FileSystem.delete() in src/platform/common/platform/fileSystem.ts (line 46) to pass the recursive option:

await this.vscfs.delete(uri, { recursive: true });

Alternatively, expose recursive as an optional parameter in the IFileSystem interface (src/platform/common/platform/types.ts line 42) for callers that need explicit control.

🤖 Prompt for AI Agents
In src/kernels/deepnote/environments/deepnoteEnvironmentManager.node.ts around
lines 196 to 203, the call to delete the virtual environment directory uses the
FileSystem.delete wrapper which by default calls VS Code's workspace.fs.delete
without recursive: true; update the underlying implementation
(src/platform/common/platform/fileSystem.ts line ~46) so it passes { recursive:
true } to vscfs.delete, or alternatively add an optional recursive parameter to
the IFileSystem.delete signature (src/platform/common/platform/types.ts ~line
42) and propagate that option through the wrapper; then call the delete here
with recursive enabled to ensure nested files are removed.

Comment on lines +200 to +203
} catch (error) {
// Log but don't fail - the directory might not exist or might already be deleted
logger.warn(`Failed to delete virtual environment directory: ${config.venvPath.fsPath}`, error);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider implications of graceful failure.

The catch block logs a warning but doesn't fail, which is reasonable for idempotency. However, if deletion consistently fails (e.g., permissions, locked files), disk space won't be reclaimed while the environment is removed from the manager's state. Consider whether this tradeoff is acceptable or if a more prominent warning to the user is warranted.

🤖 Prompt for AI Agents
In src/kernels/deepnote/environments/deepnoteEnvironmentManager.node.ts around
lines 200 to 203, the catch currently only logs a warning when venv directory
deletion fails which can hide persistent failures; update the flow so the
manager tracks deletion attempts and does not silently drop the environment
state on repeated failures — on failure increment a retry counter (persisted
in-memory or on the environment record), schedule a backoff retry for deletion,
and if the retry threshold (e.g., 3 attempts) is exceeded emit a higher-severity
log (logger.error) and a user-visible notification/event (or mark the
environment with a "deletion_failed" flag) instead of silently proceeding; keep
the operation non-crashing but ensure the state reflects the failed deletion so
operators can act.

Comment on lines +65 to +72
// Configure mockFileSystem to actually delete directories for testing
when(mockFileSystem.delete(anything())).thenCall((uri: Uri) => {
const dirPath = uri.fsPath;
if (fs.existsSync(dirPath)) {
fs.rmSync(dirPath, { recursive: true, force: true });
}
return Promise.resolve();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Mock performs actual filesystem operations.

This mock executes real filesystem deletion rather than just tracking calls. While this verifies end-to-end behavior, it crosses into integration testing territory. Consider whether this complexity is needed or if verifying the mock was called with the correct Uri is sufficient for a unit test.

Comment on lines +342 to +367
test('should delete virtual environment directory from disk', async () => {
when(mockStorage.saveEnvironments(anything())).thenResolve();

const config = await manager.createEnvironment({
name: 'Test',
pythonInterpreter: testInterpreter
});

// Create the virtual environment directory to simulate it existing
const venvDirPath = config.venvPath.fsPath;
fs.mkdirSync(venvDirPath, { recursive: true });

// Create a dummy file inside to make it a "real" directory
fs.writeFileSync(`${venvDirPath}/test.txt`, 'test content');

// Verify directory and file exist before deletion
assert.isTrue(fs.existsSync(venvDirPath), 'Directory should exist before deletion');
assert.isTrue(fs.existsSync(`${venvDirPath}/test.txt`), 'File should exist before deletion');

// Delete the environment
await manager.deleteEnvironment(config.id);

// Verify directory no longer exists (with a small delay to allow async operation)
await new Promise((resolve) => setTimeout(resolve, 100));
assert.isFalse(fs.existsSync(venvDirPath), 'Directory should not exist after deletion');
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Remove unnecessary setTimeout.

Line 365's 100ms delay is unnecessary. The mock's fs.rmSync is synchronous and wrapped in Promise.resolve(), so await manager.deleteEnvironment(config.id) on line 362 already ensures completion before the assertion.

Apply this diff:

         // Delete the environment
         await manager.deleteEnvironment(config.id);

-        // Verify directory no longer exists (with a small delay to allow async operation)
-        await new Promise((resolve) => setTimeout(resolve, 100));
         assert.isFalse(fs.existsSync(venvDirPath), 'Directory should not exist after deletion');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('should delete virtual environment directory from disk', async () => {
when(mockStorage.saveEnvironments(anything())).thenResolve();
const config = await manager.createEnvironment({
name: 'Test',
pythonInterpreter: testInterpreter
});
// Create the virtual environment directory to simulate it existing
const venvDirPath = config.venvPath.fsPath;
fs.mkdirSync(venvDirPath, { recursive: true });
// Create a dummy file inside to make it a "real" directory
fs.writeFileSync(`${venvDirPath}/test.txt`, 'test content');
// Verify directory and file exist before deletion
assert.isTrue(fs.existsSync(venvDirPath), 'Directory should exist before deletion');
assert.isTrue(fs.existsSync(`${venvDirPath}/test.txt`), 'File should exist before deletion');
// Delete the environment
await manager.deleteEnvironment(config.id);
// Verify directory no longer exists (with a small delay to allow async operation)
await new Promise((resolve) => setTimeout(resolve, 100));
assert.isFalse(fs.existsSync(venvDirPath), 'Directory should not exist after deletion');
});
test('should delete virtual environment directory from disk', async () => {
when(mockStorage.saveEnvironments(anything())).thenResolve();
const config = await manager.createEnvironment({
name: 'Test',
pythonInterpreter: testInterpreter
});
// Create the virtual environment directory to simulate it existing
const venvDirPath = config.venvPath.fsPath;
fs.mkdirSync(venvDirPath, { recursive: true });
// Create a dummy file inside to make it a "real" directory
fs.writeFileSync(`${venvDirPath}/test.txt`, 'test content');
// Verify directory and file exist before deletion
assert.isTrue(fs.existsSync(venvDirPath), 'Directory should exist before deletion');
assert.isTrue(fs.existsSync(`${venvDirPath}/test.txt`), 'File should exist before deletion');
// Delete the environment
await manager.deleteEnvironment(config.id);
assert.isFalse(fs.existsSync(venvDirPath), 'Directory should not exist after deletion');
});
🤖 Prompt for AI Agents
In src/kernels/deepnote/environments/deepnoteEnvironmentManager.unit.test.ts
around lines 342 to 367, the test includes an unnecessary await new
Promise(resolve => setTimeout(resolve, 100)) after calling await
manager.deleteEnvironment(config.id); remove that setTimeout and its comment so
the test directly asserts fs.existsSync(venvDirPath) is false immediately after
the awaited deleteEnvironment call, since the mocked fs.rmSync is synchronous
and the awaited call already guarantees completion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants