Skip to content

Conversation

@jankuca
Copy link
Contributor

@jankuca jankuca commented Oct 27, 2025

Here's a comprehensive pull request description:

Summary

Implements comprehensive orphaned process cleanup to prevent "Address already in use" errors when restarting VSCode after crashes or force quits. The cleanup system safely detects and terminates orphaned deepnote-toolkit processes (including LSP servers and Jupyter servers) from previous sessions while preserving active processes.

Problem

When VSCode crashes or is force-quit while a Deepnote notebook is open, the deepnote-toolkit server processes (main server, Python LSP server on port 2087, Jupyter server on port 8888+) become orphaned and continue holding their ports. On restart, attempting to start new servers fails with "Address already in use" errors, requiring manual process cleanup.

Solution

Implemented a multi-layered orphaned process cleanup system that runs on extension activation:

1. Port-Based Cleanup

  • LSP Server: Scans port 2087 for orphaned Python LSP servers
  • Jupyter Servers: Scans port range 8888-8895 (Jupyter increments port when busy)
  • Uses lsof (with ss fallback) on Unix, netstat on Windows
  • Only targets LISTEN sockets to avoid false positives

2. Process Name-Based Cleanup

  • Detects processes by command line patterns:
    • deepnote_toolkit server (main server)
    • pylsp on port 2087 (LSP server child process)
    • jupyter with deepnote context (Jupyter child process)
  • Windows: Two-step approach (collect all Python PIDs → verify each)
  • Unix: Pattern matching with verification

3. Two-Stage Safety Verification

Every process must pass both checks before being killed:

  1. Deepnote-Related Check (isDeepnoteRelatedProcess):

    • Windows: PowerShell CIM (WMIC fallback) to get full command line
    • Unix: ps -ww with full command line
    • Validates process is from deepnote-venvs directory or contains deepnote_toolkit
    • Uses regex path matching: /[\\/](deepnote-venvs)[\\/]/i
  2. Orphaned Check (isProcessOrphaned):

    • Checks if process has a lock file from a different session
    • Verifies parent process is init/launchd (PPID=1) indicating orphaned state
    • Skips processes with active parent processes

4. Graceful Termination

All kill operations attempt graceful termination before force kill:

  • Windows:

    • Try taskkill /T /PID (graceful)
    • Escalate to taskkill /F /T /PID on failure
    • /T flag kills entire process tree
  • Unix:

    • Send SIGTERM (signal 15)
    • Wait 1 second for graceful shutdown
    • Escalate to SIGKILL (signal 9) if still alive

5. Lock File Management

  • Creates lock files for each server process with session ID
  • Cleans up lock files after successful process termination
  • Prevents stale lock file accumulation

Key Features

Safety

  • ✅ Two-stage verification (deepnote-related AND orphaned)
  • ✅ Lock file tracking prevents killing active sessions
  • ✅ Graceful termination before force kill
  • ✅ Process tree killing on Windows (/T flag)
  • ✅ Regex path matching for robust detection
  • ✅ Won't kill external Jupyter/LSP servers

Performance

  • -nP flag on lsof (no DNS/service lookups, ~10-50x faster)
  • ✅ PID deduplication with Set<number>
  • ✅ Timing measurement for monitoring
  • ✅ CSV parsing for structured output

Robustness

  • ✅ PowerShell CIM with WMIC fallback (Windows)
  • -ww flag on ps (prevents command line truncation)
  • ✅ lsof with ss fallback (Unix)
  • ✅ LISTEN-only socket filtering
  • ✅ Safe error handling throughout

Technical Details

Windows Process Detection

// Step 1: Collect all Python PIDs
tasklist /FI "IMAGENAME eq python.exe" /FO CSV /NH
tasklist /FI "IMAGENAME eq pythonw.exe" /FO CSV /NH

// Step 2: Verify each PID with PowerShell CIM
Get-CimInstance Win32_Process -Filter "ProcessId=${pid}"
// Fallback to WMIC if PowerShell fails
wmic process where ProcessId=${pid} get CommandLine

Unix Process Detection

# Port-based with -nP flag (no DNS lookups)
lsof -sTCP:LISTEN -i :${port} -t -nP

# Fallback to ss
ss -tlnp sport = :${port}

# Process name-based with full command line
ps -ww aux

Process Liveness Check

  • Windows: CSV parsing of tasklist /FO CSV output
  • Unix: kill -0 ${pid} (signal 0 = existence check)

Testing

Tested scenarios:

  • Kill VSCode while notebook open → restart → orphaned processes cleaned
  • External Jupyter server on port 8888 → not killed (safety check)
  • Multiple orphaned processes → all cleaned up
  • Lock file cleanup after termination
  • Graceful termination (SIGTERM) before force kill (SIGKILL)
  • Process tree killing on Windows
  • Cross-platform (Windows, macOS, Linux)

Performance Impact

  • Port cleanup: ~50-200ms when no orphaned processes exist
  • Process verification: ~10-30ms per process
  • Graceful kill: ~1000ms (1s wait for SIGTERM)
  • Total typical cleanup: <500ms

Breaking Changes

None. This is purely additive functionality that runs on extension activation.

Summary by CodeRabbit

  • Bug Fixes

    • Improved detection and cleanup of orphaned "deepnote-related" background processes, adding port-targeted cleanup for LSP (2087) and Jupyter (8888–8895) and per-process verification before termination.
    • More reliable graceful-then-forceful termination across Windows and Unix-like systems; clearer reporting of skipped vs. killed processes and final cleanup confirmation.
  • Chores

    • Updated startup cleanup behavior to avoid blocking startup on errors and ensure lock-file removal.
    • Added spellchecker allowances for new tooling terms.

- Add port-based cleanup for LSP (2087) and Jupyter (8888) servers
- Add isDeepnoteRelatedProcess() to verify processes are from deepnote-venvs
- Expand process detection to include pylsp and jupyter child processes
- Prevent killing external Jupyter/LSP servers not managed by extension
- Fix 'Address already in use' errors from orphaned LSP servers

The cleanup now uses a two-stage safety check:
1. Verify process is deepnote-related (from deepnote-venvs directory)
2. Verify process is orphaned (parent no longer exists)

This prevents interference with user's manual Jupyter servers or other
tools while still cleaning up stuck processes from crashed VSCode sessions.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

📝 Walkthrough

Walkthrough

Adds robust, platform-aware orphaned-process cleanup to the Deepnote server starter. New private helpers: isDeepnoteRelatedProcess, isProcessAlive, killProcessGracefully, and cleanupProcessesByPort. cleanupOrphanedProcesses now performs port-based cleanup (port 2087 and 8888–8895) before a broader scan, filters candidates by command-line ownership, distinguishes main server processes via lock files from child processes, chooses kill strategy based on ownership/orphan status, and ensures lock-file removal and non-blocking startup behavior. No public API changes.

Sequence Diagram(s)

sequenceDiagram
    participant Startup as Server Startup
    participant PortCleaner as cleanupProcessesByPort
    participant Scanner as Broad Scanner
    participant Verifier as isDeepnoteRelatedProcess / isProcessAlive
    participant Killer as killProcessGracefully
    participant Locker as Lock-file manager

    Startup->>PortCleaner: Check ports 2087, 8888–8895
    PortCleaner->>PortCleaner: Discover listeners (netstat / lsof / ss)
    PortCleaner->>Verifier: Verify deepnote-related & alive
    alt listener is orphaned
        PortCleaner->>Killer: Attempt graceful kill -> escalate
        Killer->>Locker: Remove lock file if present
    else listener retained
        PortCleaner-->>Startup: Skip (owned/active)
    end

    Startup->>Scanner: Broad process enumeration (ps / tasklist)
    Scanner->>Verifier: Filter by command line & liveness
    Scanner->>Locker: Check lock-file ownership
    alt process is orphaned or non-owner child
        Scanner->>Killer: Attempt graceful kill -> escalate
        Killer->>Locker: Cleanup lock files
    else skip
        Scanner-->>Startup: Skip (owner/active)
    end

    Killer-->>Startup: Report killed/skipped and final status
Loading

Possibly related PRs

Suggested reviewers

  • FilipPyrek
  • Artmann

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 "fix: improve orphaned process cleanup to handle child processes safely" directly aligns with the core changes in the changeset. The primary modification to deepnoteServerStarter.node.ts is a comprehensive refactoring of orphaned process cleanup logic that introduces multiple layers of safety verification and child process handling across platforms. The title accurately captures both what is being improved (orphaned process cleanup) and the key benefit (safer child process handling), making it clear and specific without being vague or misleading.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51e0704 and 47162c6.

📒 Files selected for processing (1)
  • src/kernels/deepnote/deepnoteServerStarter.node.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.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/deepnoteServerStarter.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

**/*.{ts,tsx}: Order class members (methods, fields, properties) first by accessibility (public/protected/private) and then alphabetically
Do not add the Microsoft copyright header to new files
Use Uri.joinPath() to construct file paths instead of manual string concatenation
Add a blank line after groups of const declarations and before return statements for readability
Separate third-party imports from local file imports

Files:

  • src/kernels/deepnote/deepnoteServerStarter.node.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/deepnoteServerStarter.node.ts
🪛 ast-grep (0.39.6)
src/kernels/deepnote/deepnoteServerStarter.node.ts

[warning] 848-848: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(:${port}\\b)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build & Test
  • GitHub Check: Build & Package Extension

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

@codecov
Copy link

codecov bot commented Oct 27, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72%. Comparing base (4382e25) to head (47162c6).
⚠️ Report is 6 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@          Coverage Diff          @@
##            main    #119   +/-   ##
=====================================
  Coverage     72%     72%           
=====================================
  Files        536     536           
  Lines      40837   40837           
  Branches    4990    4990           
=====================================
  Hits       29596   29596           
  Misses      9578    9578           
  Partials    1663    1663           
🚀 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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/kernels/deepnote/deepnoteServerStarter.node.ts (1)

782-819: Gate by isDeepnoteRelatedProcess before any orphan checks/kills.

Prevents accidental kills of non-Deepnote orphaned Python daemons when Windows pushes all python.exe PIDs.

Apply:

                     // Check each process to determine if it should be killed
                     for (const pid of candidatePids) {
+                        // Final safety gate: ensure the PID is truly Deepnote-related
+                        const deepnoteRelated = await this.isDeepnoteRelatedProcess(pid);
+                        if (!deepnoteRelated) {
+                            pidsToSkip.push({ pid, reason: 'not deepnote-related' });
+                            continue;
+                        }
                         // Check if there's a lock file for this PID (only main server processes have lock files)
                         const lockData = await this.readLockFile(pid);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2fa9b5 and 887834e.

📒 Files selected for processing (1)
  • src/kernels/deepnote/deepnoteServerStarter.node.ts (7 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.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/deepnoteServerStarter.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

**/*.{ts,tsx}: Order class members (methods, fields, properties) first by accessibility (public/protected/private) and then alphabetically
Do not add the Microsoft copyright header to new files
Use Uri.joinPath() to construct file paths instead of manual string concatenation
Add a blank line after groups of const declarations and before return statements for readability
Separate third-party imports from local file imports

Files:

  • src/kernels/deepnote/deepnoteServerStarter.node.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/deepnoteServerStarter.node.ts
🧬 Code graph analysis (1)
src/kernels/deepnote/deepnoteServerStarter.node.ts (1)
src/platform/logging/index.ts (1)
  • logger (35-48)

- Use PowerShell Get-CimInstance on Windows (WMIC is deprecated)
- Add WMIC fallback for older Windows systems
- Add -ww flag to ps on Unix to prevent command line truncation
- Use regex path matching for deepnote-venvs detection
- Prevent false positives with proper path boundary matching
- Add isProcessAlive() helper to check process status cross-platform
- Implement graceful kill on Unix (SIGTERM → SIGKILL escalation)
- Implement graceful kill on Windows (taskkill → taskkill /F escalation)
- Deduplicate PIDs before processing to avoid double-kills
- Add ss fallback when lsof is unavailable on Unix
- Filter for LISTEN sockets only using lsof -sTCP:LISTEN
- Wait 1 second after SIGTERM before escalating to SIGKILL
- Improve error handling and logging throughout cleanup process
- Change from single port 8888 to range 8888-8895
- Jupyter increments port when 8888 is busy, so we need to check multiple ports
- Add timing measurement for port-based cleanup phase
- Log cleanup duration for performance monitoring

The loop adds minimal overhead as each port check is fast when no process
is listening. Timing is logged at debug level for monitoring.
- Windows tasklist CSV output never includes command lines
- Changed to two-step approach: collect all python.exe/pythonw.exe PIDs first
- Then resolve each PID's command line using isDeepnoteRelatedProcess()
- Only verified deepnote-related processes are added to candidate list
- Kill logic remains gated by isDeepnoteRelatedProcess + orphan checks
- Fixes false negatives where deepnote processes were missed on Windows
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/kernels/deepnote/deepnoteServerStarter.node.ts (1)

521-563: Use PowerShell CIM for PPID; WMIC is deprecated/missing on many systems.

Prefer CIM, fallback to WMIC; then check parent existence. Keeps orphaning accurate on Win10/11.

Apply:

-            if (process.platform === 'win32') {
-                // Windows: use WMIC to get parent process ID
-                const result = await processService.exec(
-                    'wmic',
-                    ['process', 'where', `ProcessId=${pid}`, 'get', 'ParentProcessId'],
-                    { throwOnStdErr: false }
-                );
-
-                if (result.stdout) {
-                    const lines = result.stdout
-                        .split('\n')
-                        .filter((line) => line.trim() && !line.includes('ParentProcessId'));
-                    if (lines.length > 0) {
-                        const ppid = parseInt(lines[0].trim(), 10);
-                        if (!isNaN(ppid)) {
-                            // PPID of 0 means orphaned
-                            if (ppid === 0) {
-                                return true;
-                            }
-
-                            // Check if parent process exists
-                            const parentCheck = await processService.exec(
-                                'tasklist',
-                                ['/FI', `PID eq ${ppid}`, '/FO', 'CSV', '/NH'],
-                                { throwOnStdErr: false }
-                            );
-
-                            // Normalize and check stdout
-                            const stdout = (parentCheck.stdout || '').trim();
-
-                            // Parent is missing if:
-                            // 1. stdout is empty
-                            // 2. stdout starts with "INFO:" (case-insensitive)
-                            // 3. stdout contains "no tasks are running" (case-insensitive)
-                            if (stdout.length === 0 || /^INFO:/i.test(stdout) || /no tasks are running/i.test(stdout)) {
-                                return true; // Parent missing, process is orphaned
-                            }
-
-                            // Parent exists
-                            return false;
-                        }
-                    }
-                }
-            } else {
+            if (process.platform === 'win32') {
+                // Windows: prefer PowerShell CIM, fallback to WMIC
+                let ppid: number | undefined;
+                try {
+                    const ps = await processService.exec(
+                        'powershell.exe',
+                        ['-NoProfile', '-Command', `(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").ParentProcessId`],
+                        { throwOnStdErr: false }
+                    );
+                    const raw = (ps.stdout || '').trim();
+                    const parsed = parseInt(raw, 10);
+                    if (!isNaN(parsed)) ppid = parsed;
+                } catch {}
+                if (ppid === undefined) {
+                    const wmic = await processService.exec(
+                        'wmic',
+                        ['process', 'where', `ProcessId=${pid}`, 'get', 'ParentProcessId'],
+                        { throwOnStdErr: false }
+                    );
+                    const lines = (wmic.stdout || '')
+                        .split('\n')
+                        .map((l) => l.trim())
+                        .filter((l) => l && !/^ParentProcessId/i.test(l));
+                    const parsed = lines.length ? parseInt(lines[0], 10) : NaN;
+                    if (!isNaN(parsed)) ppid = parsed;
+                }
+                if (ppid !== undefined) {
+                    if (ppid === 0) return true;
+                    const parentCheck = await processService.exec(
+                        'tasklist',
+                        ['/FI', `PID eq ${ppid}`, '/FO', 'CSV', '/NH'],
+                        { throwOnStdErr: false }
+                    );
+                    const stdout = (parentCheck.stdout || '').trim();
+                    if (stdout.length === 0 || /^"?INFO:/i.test(stdout) || /no tasks are running/i.test(stdout)) {
+                        return true;
+                    }
+                    return false;
+                }
+            } else {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 887834e and 38f3a8e.

📒 Files selected for processing (1)
  • src/kernels/deepnote/deepnoteServerStarter.node.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.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/deepnoteServerStarter.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

**/*.{ts,tsx}: Order class members (methods, fields, properties) first by accessibility (public/protected/private) and then alphabetically
Do not add the Microsoft copyright header to new files
Use Uri.joinPath() to construct file paths instead of manual string concatenation
Add a blank line after groups of const declarations and before return statements for readability
Separate third-party imports from local file imports

Files:

  • src/kernels/deepnote/deepnoteServerStarter.node.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/deepnoteServerStarter.node.ts
🧬 Code graph analysis (1)
src/kernels/deepnote/deepnoteServerStarter.node.ts (2)
src/platform/logging/index.ts (1)
  • logger (35-48)
build/launchWebUtils.js (1)
  • port (20-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build & Test
🔇 Additional comments (3)
src/kernels/deepnote/deepnoteServerStarter.node.ts (3)

593-645: Process-origin detection looks solid.

PS CIM fallback + ps -ww + venv regex = robust. Please sanity‑test on Windows 11 and macOS to confirm long argv and WMIC‑absent cases.


673-699: Graceful kill helper is good.

TERM then KILL with a short wait is fine.


851-853: Nice: port sweep and Windows Python PID discovery.

Range scan 8888–8895 and 2‑step Windows candidate build look good.

Also applies to: 912-920

- Windows tasklist substring match was brittle and unreliable
- Changed to use /FO CSV option for structured output
- Parse CSV rows to extract PID from second column
- Use exact numeric equality comparison instead of substring match
- Skip INFO: messages and empty lines in output
- Handle parse errors safely with try-catch per line
- Return false on any failure while preserving throwOnStdErr: false
- More robust and accurate process existence detection on Windows
- Add -nP flag to lsof invocations to avoid slow DNS/service lookups
- Clean up lock files after successful process kills in port cleanup logic
- Windows: cleanup lock files after both graceful and force kills
- Unix: cleanup lock files after graceful kill completes
- Uses existing deleteLockFile() helper which safely checks existence
- Prevents stale lock files from accumulating after cleanup operations
- Improves performance by avoiding unnecessary DNS reverse lookups
- Unix branch was using only string matching to add PIDs to candidates
- Now validates each PID with isDeepnoteRelatedProcess() before adding
- Added re-verification in kill/lock loop before any action
- Matches Windows two-stage verification pattern
- Prevents false positives from ps output string matching
- Ensures only verified deepnote-related processes are considered
- Adds safety check in case process changes between detection and action
- Changed from immediate force kill to graceful termination first
- Windows: try taskkill without /F first, escalate to /F on failure
- Unix: use existing killProcessGracefully helper (SIGTERM then SIGKILL)
- Matches the graceful termination pattern from port-based cleanup
- Preserves throwOnStdErr: false behavior throughout
- Gives processes a chance to clean up before force termination
- More consistent behavior across all cleanup code paths
- pylsp: Python Language Server Protocol (LSP server)
- pythonw: Windows Python executable without console window
- tlnp: ss command flag for TCP listening numeric ports
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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/kernels/deepnote/deepnoteServerStarter.node.ts (1)

520-563: Replace WMIC + localized tasklist checks with PowerShell CIM for orphan detection.

WMIC is deprecated and tasklist “INFO:” parsing is locale‑sensitive. Use CIM for PPID and parent liveness to harden this path.

Apply:

-            if (process.platform === 'win32') {
-                // Windows: use WMIC to get parent process ID
-                const result = await processService.exec(
-                    'wmic',
-                    ['process', 'where', `ProcessId=${pid}`, 'get', 'ParentProcessId'],
-                    { throwOnStdErr: false }
-                );
-                if (result.stdout) {
-                    const lines = result.stdout
-                        .split('\n')
-                        .filter((line) => line.trim() && !line.includes('ParentProcessId'));
-                    if (lines.length > 0) {
-                        const ppid = parseInt(lines[0].trim(), 10);
-                        if (!isNaN(ppid)) {
-                            // PPID of 0 means orphaned
-                            if (ppid === 0) {
-                                return true;
-                            }
-
-                            // Check if parent process exists
-                            const parentCheck = await processService.exec(
-                                'tasklist',
-                                ['/FI', `PID eq ${ppid}`, '/FO', 'CSV', '/NH'],
-                                { throwOnStdErr: false }
-                            );
-                            const stdout = (parentCheck.stdout || '').trim();
-                            if (stdout.length === 0 || /^INFO:/i.test(stdout) || /no tasks are running/i.test(stdout)) {
-                                return true; // Parent missing, process is orphaned
-                            }
-                            return false;
-                        }
-                    }
-                }
-            } else {
+            if (process.platform === 'win32') {
+                // Windows: prefer CIM for PPID and parent liveness
+                let ppid = NaN;
+                try {
+                    const p = await processService.exec(
+                        'powershell.exe',
+                        ['-NoProfile','-Command', `(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").ParentProcessId`],
+                        { throwOnStdErr: false }
+                    );
+                    ppid = parseInt((p.stdout || '').trim(), 10);
+                } catch {
+                    ppid = NaN;
+                }
+                if (!isNaN(ppid)) {
+                    if (ppid === 0) return true; // no parent
+                    try {
+                        const parent = await processService.exec(
+                            'powershell.exe',
+                            ['-NoProfile','-Command', `(Get-CimInstance Win32_Process -Filter "ProcessId=${ppid}") | Select-Object -ExpandProperty ProcessId`],
+                            { throwOnStdErr: false }
+                        );
+                        const exists = (parent.stdout || '').trim().length > 0;
+                        return !exists;
+                    } catch {
+                        return true; // if we can’t query parent, treat as orphan for safety
+                    }
+                }
+            } else {

As per coding guidelines.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38f3a8e and 51e0704.

📒 Files selected for processing (2)
  • cspell.json (1 hunks)
  • src/kernels/deepnote/deepnoteServerStarter.node.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.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/deepnoteServerStarter.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

**/*.{ts,tsx}: Order class members (methods, fields, properties) first by accessibility (public/protected/private) and then alphabetically
Do not add the Microsoft copyright header to new files
Use Uri.joinPath() to construct file paths instead of manual string concatenation
Add a blank line after groups of const declarations and before return statements for readability
Separate third-party imports from local file imports

Files:

  • src/kernels/deepnote/deepnoteServerStarter.node.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/deepnoteServerStarter.node.ts
🧬 Code graph analysis (1)
src/kernels/deepnote/deepnoteServerStarter.node.ts (1)
src/platform/logging/index.ts (1)
  • logger (35-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build & Package Extension
  • GitHub Check: Build & Test
🔇 Additional comments (1)
cspell.json (1)

49-55: LGTM.

New words align with usage (pylsp/pythonw/ss -tlnp). No issues.

- Changed from substring match to regex-based port extraction
- Prevents false positives (e.g., port 8888 matching 88880)
- Parses LocalAddress column and extracts port number explicitly
- Handles both IPv4 (0.0.0.0:8888) and IPv6 ([::]:8888) formats
- Uses exact numeric equality comparison instead of string contains
- More robust and accurate port-based process detection
- Some ss builds don't support 'sport = :port' filter syntax
- Detect filter failure (empty stdout, stderr mentions 'filter')
- Fallback to unfiltered 'ss -tlnp' and parse full output
- Use regex to match exact port with word boundary (e.g., :8888\b)
- Extract pid=NUMBER from matching lines only
- Prevents false positives (port 8888 won't match 88880)
- Maintains Set<number> for automatic PID deduplication
- Preserves throwOnStdErr: false throughout
- Handles null/empty output safely
@jankuca
Copy link
Contributor Author

jankuca commented Oct 27, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 27, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jankuca jankuca marked this pull request as ready for review October 27, 2025 13:31
Copy link
Contributor

@saltenasl saltenasl left a comment

Choose a reason for hiding this comment

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

this doesn't work, i force quit code which then meant kernel never started for me anymore until i manually killed the process running on port 2087 and then it started working again. The error was in starting LSP via toolkit (I haven't copied it unfortunately)

@jankuca jankuca closed this Oct 29, 2025
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.

3 participants