Skip to content

feat(desktop): replace regex-based port detection with process-based scanning#610

Merged
Kitenite merged 3 commits into
mainfrom
ports-shiz
Jan 6, 2026
Merged

feat(desktop): replace regex-based port detection with process-based scanning#610
Kitenite merged 3 commits into
mainfrom
ports-shiz

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Jan 6, 2026

Summary

  • Fixes port detection for TUI apps like turborepo that use cursor positioning instead of linear output
  • Replaces fragile regex-based terminal output parsing with reliable process-based scanning using lsof (macOS/Linux) and netstat (Windows)

Changes

  • Add pidtree dependency for cross-platform process tree traversal
  • Create port-scanner.ts with platform-specific utilities for finding listening ports
  • Create port-hints.ts for lightweight output hint detection to trigger immediate scans
  • Rewrite port-manager.ts with new architecture:
    • Periodic scanning every 2.5s of all registered terminal sessions
    • Immediate scanning triggered by output hints (localhost:PORT, etc.)
    • Process tree traversal to find all child processes listening on ports
  • Update DetectedPort type with pid, processName, and address fields
  • Update UI tooltip to show process name instead of context line

Summary by CodeRabbit

  • New Features

    • Port list now shows process names and PIDs for each detected port.
    • Cross-platform process-based port discovery added.
  • Improvements

    • More reliable port detection via periodic, session-scoped scanning and hint-triggered scans.
    • Scans are now triggered intelligently by terminal output hints to reduce unnecessary work.
    • Improved spacing for port indicators in the sidebar.
  • Tests

    • Added unit tests for port discovery/parsing.
  • Chores

    • Added new runtime dependency for process tree inspection.

✏️ Tip: You can customize this high-level summary in your review settings.

…scanning

- Add pidtree dependency for cross-platform process tree traversal
- Create port-scanner.ts with lsof (macOS/Linux) and netstat (Windows) support
- Create port-hints.ts for lightweight output hint detection to trigger immediate scans
- Rewrite port-manager.ts with process-based architecture:
  - Periodic scanning every 2.5s of all registered terminal sessions
  - Immediate scanning triggered by output hints (localhost:PORT, etc.)
  - Process tree traversal to find all child processes listening on ports
- Update DetectedPort type with pid, processName, and address fields
- Update UI tooltip to show process name instead of context line

This fixes port detection for TUI apps like turborepo that use cursor
positioning instead of linear output, making regex parsing unreliable.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

Refactors terminal port detection from line-based regex and per-port probes to a session-driven, process-tree scanning system with periodic and hint-triggered scans; adds cross-platform port-scanning utilities and tests, introduces pidtree dependency, updates session/manager integration, types, and UI to include PID/process name and address.

Changes

Cohort / File(s) Summary
Dependency
apps/desktop/package.json
Added dependency pidtree (^0.6.0).
Port discovery & tests
apps/desktop/src/main/lib/terminal/port-scanner.ts, apps/desktop/src/main/lib/terminal/port-scanner.test.ts
New cross-platform port-scanning module (lsof/netstat parsing, getProcessTree, getListeningPortsForPids, getProcessName, PortInfo) with comprehensive unit tests for lsof parsing.
Port hinting
apps/desktop/src/main/lib/terminal/port-hints.ts
New lightweight hint-detection utility (containsPortHint) with regex patterns for spotting probable port-opening output.
Port manager refactor
apps/desktop/src/main/lib/terminal/port-manager.ts
Replaced per-port health checks and line parsing with session registration/unregistration, periodic global scans, hint-debounced scans, aggregated per-pane DetectedPort maps, events (port:add/port:remove), and public query/force-scan APIs.
Terminal integration
apps/desktop/src/main/lib/terminal/manager.ts, apps/desktop/src/main/lib/terminal/session.ts
Register sessions at creation and unregister on exit (registerSession / unregisterSession); session output now calls checkOutputForHint() instead of scanning every data chunk; removed immediate recovered-scrollback scanning.
Types & UI
apps/desktop/src/shared/types/ports.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
DetectedPort schema changed: added pid:number, processName:string, address:string (removed contextLine); PortsList tooltip now shows processName and PID and layout spacing adjusted (mb-2).
Misc / minor
apps/desktop/src/lib/trpc/routers/projects/projects.ts
Removed inline comments in GitHub avatar resolver (no functional change).

Sequence Diagram(s)

sequenceDiagram
  participant Session as TerminalSession
  participant Manager as TerminalManager
  participant PM as PortManager
  participant Scanner as PortScanner
  participant UI as Renderer/UI

  Note over Session,Manager: Session lifecycle
  Session->>Manager: createSession()
  Manager->>PM: registerSession(session, workspaceId)

  Note right of Session: terminal output stream
  Session->>PM: checkOutputForHint(data, paneId)
  alt hint detected
    PM->>PM: debounce & schedule scan(paneId)
    PM->>Scanner: getProcessTree(session.pid)
    Scanner-->>PM: pid[]
    PM->>Scanner: getListeningPortsForPids(pid[])
    Scanner-->>PM: PortInfo[]
    PM->>PM: updatePortsForPane(paneId, PortInfo[])
    PM-->>UI: emit port:add / port:remove events
    UI->>UI: update PortsList (show processName, pid)
  end

  Session->>Manager: onExit()
  Manager->>PM: unregisterSession(paneId)
  PM->>PM: remove ports for paneId & emit port:remove
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I sniff the process tree by moonlit code,
Hints flutter like leaves where ports once showed.
Sessions register, scanners softly hum—
PIDs and names parade, discoveries come.
A joyful hop: new ports begin to glow.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% 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
Title check ✅ Passed The pull request title clearly and concisely summarizes the main change: replacing regex-based port detection with process-based scanning, which aligns perfectly with the primary objective of the changeset.
Description check ✅ Passed The pull request description covers all required template sections including Summary, Changes, Related Issues (not applicable), Type of Change (feature), and Additional Notes, providing comprehensive context for the changes.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
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: 3

🤖 Fix all issues with AI Agents
In @apps/desktop/src/main/lib/terminal/port-hints.ts:
- Around line 23-29: The framework regexes in the framework-specific patterns
array in port-hints.ts (the entries /vite/i, /express/i, /fastify/i) are too
broad and cause false positives; tighten them to require startup/context words
(e.g., require "dev", "server", "listening", "started", or similar) or combine
them with the existing startup message patterns above so the match only triggers
on actual server start lines—update the /vite/i, /express/i, and /fastify/i
entries to context-aware patterns (or add word-boundary/anchoring) so only
startup messages are matched.

In @apps/desktop/src/main/lib/terminal/port-manager.ts:
- Around line 69-72: The setTimeout callback currently calls
this.scanPane(paneId).catch(() => {}) which silently swallows errors; change it
to catch the error and log it with context (include paneId and that it occurred
during a hint scan) instead of an empty handler—update the promise error handler
on the this.scanPane(paneId) call so it logs the error via the project's logger
(or console.error) and still prevents unhandled rejections while referencing
pendingHintScans, scanPane, paneId and HINT_SCAN_DELAY_MS.

In @apps/desktop/src/main/lib/terminal/port-scanner.ts:
- Around line 73-94: The lsof parsing incorrectly uses columns[columns.length -
1] (which is "(LISTEN)") for the NAME field; change the code that assigns name
to use columns[columns.length - 2] (and guard for columns.length < 2) so you
extract the address:port token (e.g., "*:3000") before running the existing
regex and pushing into ports; keep the rest of the parsing (regex match, port
validation, ports.push with address mapping) unchanged.
🧹 Nitpick comments (6)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx (1)

202-202: Inconsistent spacing: use parent gap instead of per-badge margin.

The parent container at line 152 uses gap-1 for horizontal spacing between badges, but adding mb-2 here creates inconsistent spacing (1 unit horizontal vs 2 units vertical). Consider removing mb-2 and adjusting the parent's gap-1 to handle both dimensions, or use gap-x-1 gap-y-2 on the parent for consistent control.

🔎 Proposed fix for consistent spacing

Remove mb-2 from the badge:

 				<div
-					className={`group relative inline-flex items-center gap-1 rounded-md text-xs font-mono transition-colors mb-2 ${
+					className={`group relative inline-flex items-center gap-1 rounded-md text-xs font-mono transition-colors ${
 						isCurrentWorkspace

And update the parent container at line 152:

-			<div className="flex flex-wrap gap-1 px-3">
+			<div className="flex flex-wrap gap-x-1 gap-y-2 px-3">
apps/desktop/src/main/lib/terminal/port-hints.ts (1)

36-41: Extract magic number to named constant.

The threshold value 10 at line 38 should be extracted to a named constant at the module top for clarity and maintainability. As per coding guidelines, magic numbers should be extracted to named constants.

🔎 Proposed fix
+/** Minimum data length to check for port hints - shorter output unlikely to contain port info */
+const MIN_HINT_DATA_LENGTH = 10;
+
 /**
  * Check if terminal output contains hints that a port may have been opened.
  * This is a lightweight check - false positives are acceptable since we verify
  * with actual process scanning.
  */
 export function containsPortHint(data: string): boolean {
 	// Quick length check - very short output unlikely to contain port info
-	if (data.length < 10) return false;
+	if (data.length < MIN_HINT_DATA_LENGTH) return false;
 
 	return HINT_PATTERNS.some((pattern) => pattern.test(data));
 }
apps/desktop/src/main/lib/terminal/port-scanner.ts (2)

46-59: Command injection risk with PID interpolation.

While PIDs from pidtree should be numbers, directly interpolating them into a shell command string could be risky if the input is ever compromised. Consider using execFile with an array of arguments instead of execSync with shell interpolation for better security and to avoid shell parsing overhead.

Additionally, execSync blocks the Node.js event loop during execution. For potentially long-running scans with many PIDs, this could impact UI responsiveness.

🔎 Suggested safer approach using execFileSync
-import { execSync } from "node:child_process";
+import { execFileSync } from "node:child_process";
 function getListeningPortsLsof(pids: number[]): PortInfo[] {
 	try {
 		const pidArg = pids.join(",");
-		// -p: filter by PIDs
-		// -iTCP: only TCP connections
-		// -sTCP:LISTEN: only listening sockets
-		// -P: don't convert port numbers to names
-		// -n: don't resolve hostnames
-		// -F: output in parseable format (pid, command, name fields)
-		const output = execSync(
-			`lsof -p ${pidArg} -iTCP -sTCP:LISTEN -P -n 2>/dev/null || true`,
-			{ encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 },
-		);
+		const output = execFileSync(
+			"lsof",
+			["-p", pidArg, "-iTCP", "-sTCP:LISTEN", "-P", "-n"],
+			{ encoding: "utf-8", maxBuffer: 10 * 1024 * 1024, stdio: ["pipe", "pipe", "ignore"] },
+		);

162-187: Good fallback pattern for deprecated wmic.

The PowerShell fallback handles the wmic deprecation well. However, the same command injection concern applies here - consider validating PIDs are positive integers before interpolation.

🔎 Optional: Add PID validation
 function getProcessNameWindows(pid: number): string {
+	// Validate PID is a positive integer
+	if (!Number.isInteger(pid) || pid <= 0) {
+		return "unknown";
+	}
+
 	try {
 		const output = execSync(
apps/desktop/src/main/lib/terminal/port-manager.ts (2)

29-32: Consider lazy initialization of periodic scan.

The periodic scan starts immediately in the constructor, even when no sessions are registered. This means the scan interval runs continuously from app startup. Consider starting the scan only when the first session is registered.

🔎 Suggested lazy start approach
 	constructor() {
 		super();
-		this.startPeriodicScan();
+		// Scan will start when first session is registered
 	}
 
 	/**
 	 * Register a terminal session for port scanning
 	 */
 	registerSession(session: TerminalSession, workspaceId: string): void {
 		this.sessions.set(session.paneId, { session, workspaceId });
+		
+		// Start scanning if this is the first session
+		if (this.sessions.size === 1 && !this.scanInterval) {
+			this.startPeriodicScan();
+		}
 	}

146-158: Consider parallelizing process tree fetches.

The sequential await getProcessTree(pid) for each session could be slow with many terminal sessions. Consider using Promise.all to fetch process trees in parallel.

🔎 Suggested parallel approach
-		for (const [paneId, { session, workspaceId }] of this.sessions) {
-			if (!session.isAlive) continue;
-
-			try {
-				const pid = session.pty.pid;
-				const pids = await getProcessTree(pid);
-				if (pids.length > 0) {
-					panePortMap.set(paneId, { workspaceId, pids });
-				}
-			} catch {
-				// Session may have exited
-			}
-		}
+		const sessionEntries = Array.from(this.sessions.entries())
+			.filter(([, { session }]) => session.isAlive);
+
+		const results = await Promise.all(
+			sessionEntries.map(async ([paneId, { session, workspaceId }]) => {
+				try {
+					const pid = session.pty.pid;
+					const pids = await getProcessTree(pid);
+					return pids.length > 0 ? { paneId, workspaceId, pids } : null;
+				} catch {
+					return null;
+				}
+			})
+		);
+
+		for (const result of results) {
+			if (result) {
+				panePortMap.set(result.paneId, { workspaceId: result.workspaceId, pids: result.pids });
+			}
+		}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8712d3 and eef17eb.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • apps/desktop/package.json
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/port-hints.ts
  • apps/desktop/src/main/lib/terminal/port-manager.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.ts
  • apps/desktop/src/main/lib/terminal/session.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
  • apps/desktop/src/shared/types/ports.ts
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/main/lib/terminal/port-hints.ts
  • apps/desktop/src/shared/types/ports.ts
  • apps/desktop/src/main/lib/terminal/session.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
  • apps/desktop/src/main/lib/terminal/port-manager.ts
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • apps/desktop/src/main/lib/terminal/port-hints.ts
  • apps/desktop/src/shared/types/ports.ts
  • apps/desktop/src/main/lib/terminal/session.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
  • apps/desktop/src/main/lib/terminal/port-manager.ts
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for all database operations - never use raw SQL

Files:

  • apps/desktop/src/main/lib/terminal/port-hints.ts
  • apps/desktop/src/shared/types/ports.ts
  • apps/desktop/src/main/lib/terminal/session.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
  • apps/desktop/src/main/lib/terminal/port-manager.ts
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • apps/desktop/src/main/lib/terminal/port-hints.ts
  • apps/desktop/src/shared/types/ports.ts
  • apps/desktop/src/main/lib/terminal/session.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
  • apps/desktop/src/main/lib/terminal/port-manager.ts
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.ts
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

One component per file - do not create multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx
🧠 Learnings (1)
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility

Applied to files:

  • apps/desktop/src/main/lib/terminal/port-scanner.ts
🧬 Code graph analysis (2)
apps/desktop/src/main/lib/terminal/session.ts (1)
apps/desktop/src/main/lib/terminal/port-manager.ts (1)
  • portManager (293-293)
apps/desktop/src/main/lib/terminal/port-manager.ts (4)
apps/desktop/src/main/lib/terminal/types.ts (1)
  • TerminalSession (5-22)
apps/desktop/src/shared/types/ports.ts (1)
  • DetectedPort (1-9)
apps/desktop/src/main/lib/terminal/port-hints.ts (1)
  • containsPortHint (36-41)
apps/desktop/src/main/lib/terminal/port-scanner.ts (2)
  • getProcessTree (15-22)
  • getListeningPortsForPids (28-41)
⏰ 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). (6)
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy Docs
  • GitHub Check: Deploy API
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy Web
  • GitHub Check: Build
🔇 Additional comments (13)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/PortsList/PortsList.tsx (1)

228-230: LGTM!

The tooltip correctly displays the new process-based port metadata (processName and pid), which aligns with the refactored DetectedPort type and provides more actionable information than the previous contextLine.

apps/desktop/src/shared/types/ports.ts (1)

1-9: LGTM!

The updated DetectedPort interface correctly reflects the shift to process-based detection. The new fields (pid, processName, address) provide richer metadata, and replacing contextLine with structured data is a solid improvement.

apps/desktop/src/main/lib/terminal/session.ts (2)

99-100: LGTM!

Clear comment explaining the architectural shift from scrollback-based port scanning to process-based detection via PortManager.


167-168: LGTM!

The integration with portManager.checkOutputForHint correctly replaces inline port scanning with hint-based triggering, aligning with the process-based detection architecture.

apps/desktop/package.json (1)

95-95: pidtree package version 0.6.0 is valid and secure.

The version is available on npm as the latest release (published 2022-06-05) and has no known security vulnerabilities reported.

apps/desktop/src/main/lib/terminal/manager.ts (2)

95-97: LGTM - Port manager registration properly placed.

The session is registered with the port manager after it's created and stored in the sessions map, ensuring the session is fully initialized before port scanning begins.


148-149: LGTM - Clean unregistration on session exit.

Properly unregisters the session before emitting the exit event, ensuring ports are cleaned up synchronously when the terminal exits.

apps/desktop/src/main/lib/terminal/port-scanner.ts (2)

105-157: Windows netstat parsing looks correct.

The column extraction properly handles the netstat output format. The process name caching is a good optimization to avoid repeated wmic/PowerShell calls.


192-211: LGTM - Cross-platform process name lookup.

The implementation correctly delegates to the appropriate platform-specific method. The comment about macOS comm truncation is helpful context.

apps/desktop/src/main/lib/terminal/port-manager.ts (4)

112-129: LGTM - Scan implementation with proper error handling.

The try/catch properly handles the potential race condition where a session could exit between the isAlive check and accessing pty.pid. The error logging provides good context for debugging.


181-244: LGTM - Port update logic handles all cases correctly.

The implementation properly handles:

  • New port detection with port:add event
  • Port updates (when PID/process changes) via port:remove + port:add
  • Stale port cleanup for ports no longer listening

The seenKeys tracking ensures accurate diff detection.


253-290: LGTM - Helper methods are well implemented.

The port removal, querying, and force scan methods are straightforward and correctly implemented.


7-15: Good use of named constants for configuration.

The constants SCAN_INTERVAL_MS, HINT_SCAN_DELAY_MS, and IGNORED_PORTS are well-named and appropriately placed at module top, following the coding guidelines for extracting magic numbers.

Comment on lines +23 to +29
// Framework-specific patterns
/next\.?js/i, // Next.js
/vite/i, // Vite
/webpack.*compiled/i, // Webpack dev server
/express/i, // Express
/fastify/i, // Fastify
];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Overly broad framework patterns may trigger excessive false-positive scans.

While the comment notes that false positives are acceptable, patterns like /vite/i, /express/i, and /fastify/i match the framework name anywhere in output—including error messages, file paths, or unrelated text. This could trigger unnecessary process scans frequently, impacting performance.

Consider making these patterns more specific by requiring context (e.g., /vite.*(?:dev|server)/i, /express.*(?:listening|server)/i) or combining them with the startup message patterns above.

🔎 Proposed more specific patterns
 	// Framework-specific patterns
-	/next\.?js/i, // Next.js
-	/vite/i, // Vite
+	/next(?:\.js)?\s+(?:dev|server|ready)/i, // Next.js
+	/vite\s+(?:dev|server|preview)/i, // Vite
 	/webpack.*compiled/i, // Webpack dev server
-	/express/i, // Express
-	/fastify/i, // Fastify
+	/express.*(?:listening|started|server)/i, // Express
+	/fastify.*(?:listening|started|server)/i, // Fastify
📝 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
// Framework-specific patterns
/next\.?js/i, // Next.js
/vite/i, // Vite
/webpack.*compiled/i, // Webpack dev server
/express/i, // Express
/fastify/i, // Fastify
];
// Framework-specific patterns
/next(?:\.js)?\s+(?:dev|server|ready)/i, // Next.js
/vite\s+(?:dev|server|preview)/i, // Vite
/webpack.*compiled/i, // Webpack dev server
/express.*(?:listening|started|server)/i, // Express
/fastify.*(?:listening|started|server)/i, // Fastify
];
🤖 Prompt for AI Agents
In @apps/desktop/src/main/lib/terminal/port-hints.ts around lines 23-29, The
framework regexes in the framework-specific patterns array in port-hints.ts (the
entries /vite/i, /express/i, /fastify/i) are too broad and cause false
positives; tighten them to require startup/context words (e.g., require "dev",
"server", "listening", "started", or similar) or combine them with the existing
startup message patterns above so the match only triggers on actual server start
lines—update the /vite/i, /express/i, and /fastify/i entries to context-aware
patterns (or add word-boundary/anchoring) so only startup messages are matched.

Comment thread apps/desktop/src/main/lib/terminal/port-scanner.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 6, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

Thank you for your contribution! 🎉

Copy link
Copy Markdown
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: 2

🤖 Fix all issues with AI Agents
In @apps/desktop/src/main/lib/terminal/port-manager.ts:
- Around line 145-153: The catch block swallowing errors around the process-tree
scan should log the error with context instead of being empty; update the
try/catch around getProcessTree(pid) (where pid is taken from session.pty.pid)
to catch (err) and call the module logger (or processLogger) to record a clear
message including paneId, workspaceId and the pid and the error object so
unexpected failures are visible while preserving the existing comment about
sessions exiting.
- Around line 68-71: Replace the silent .catch(() => {}) on the
this.scanPane(paneId) promise with an error-logging handler that records the
error and context instead of swallowing it; specifically, in the timeout
callback where timeout is set and this.pendingHintScans.delete(paneId) is
called, change the catch to something like .catch(err => { /* use existing
logger (e.g. processLogger.error) or console.error */ }) and include paneId and
that the failure came from a scheduled hint scan (HINT_SCAN_DELAY_MS) in the log
message so the error is observable while preserving non-throwing behavior.
🧹 Nitpick comments (4)
apps/desktop/src/main/lib/terminal/port-scanner.test.ts (1)

14-76: Consider reducing code duplication between test and implementation.

The PortInfo interface (lines 14-19) and parseLsofOutput function (lines 30-76) duplicate logic from port-scanner.ts. If the parsing implementation changes, these tests won't catch regressions unless the test copy is manually kept in sync.

Options to reduce duplication:

  1. Export parseLsofOutput from port-scanner.ts as a public utility and import it here
  2. Test indirectly through the public getListeningPortsForPids() API with mocked execSync
  3. Keep as-is if isolating parsing logic for unit tests is preferred, but add a comment noting the duplication risk

The test coverage itself is excellent—just flag the maintenance risk.

apps/desktop/src/main/lib/terminal/port-scanner.ts (1)

15-22: Consider logging errors in catch blocks for observability.

The empty catch block returns an empty array when pidtree fails, which is reasonable when a process has exited. However, per coding guidelines, even expected failures benefit from logging to aid debugging. Consider logging at debug level:

} catch (error) {
  // Process may have exited - this is expected
  console.debug(`[PortScanner] getProcessTree failed for pid ${pid}:`, error);
  return [];
}

The same pattern could be applied to lines 103, 157, 176-184, and 208.

apps/desktop/src/main/lib/terminal/port-manager.ts (2)

37-39: Use object parameters for functions with 2+ arguments.

Per coding guidelines, functions with 2+ parameters should accept a single params object for self-documentation and extensibility.

🔎 Proposed refactor
 	/**
 	 * Register a terminal session for port scanning
 	 */
-	registerSession(session: TerminalSession, workspaceId: string): void {
-		this.sessions.set(session.paneId, { session, workspaceId });
+	registerSession({ session, workspaceId }: { session: TerminalSession; workspaceId: string }): void {
+		this.sessions.set(session.paneId, { session, workspaceId });
 	}

Based on coding guidelines.


175-184: Use object parameters for functions with 3+ arguments.

This private method has 3 parameters and would benefit from an object parameter for clarity and extensibility, per coding guidelines.

🔎 Proposed refactor
 	/**
 	 * Update ports for a specific pane, emitting add/remove events as needed
 	 */
 	private updatePortsForPane(
-		paneId: string,
-		workspaceId: string,
-		portInfos: Array<{
+		{ paneId, workspaceId, portInfos }: {
+			paneId: string;
+			workspaceId: string;
+			portInfos: Array<{
 			port: number;
 			pid: number;
 			address: string;
 			processName: string;
-		}>,
+		}>},
 	): void {

Then update the call sites at lines 123 and 158.

Based on coding guidelines.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7172185 and 9594450.

📒 Files selected for processing (6)
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/port-hints.ts
  • apps/desktop/src/main/lib/terminal/port-manager.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.test.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/main/lib/terminal/manager.ts
  • apps/desktop/src/main/lib/terminal/port-hints.ts
🧰 Additional context used
📓 Path-based instructions (5)
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/main/lib/terminal/port-scanner.test.ts
  • apps/desktop/src/main/lib/terminal/port-manager.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • apps/desktop/src/main/lib/terminal/port-scanner.test.ts
  • apps/desktop/src/main/lib/terminal/port-manager.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Co-locate tests with implementation files using .test.ts or .test.tsx suffix

Files:

  • apps/desktop/src/main/lib/terminal/port-scanner.test.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for all database operations - never use raw SQL

Files:

  • apps/desktop/src/main/lib/terminal/port-scanner.test.ts
  • apps/desktop/src/main/lib/terminal/port-manager.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • apps/desktop/src/main/lib/terminal/port-scanner.test.ts
  • apps/desktop/src/main/lib/terminal/port-manager.ts
  • apps/desktop/src/main/lib/terminal/port-scanner.ts
🧠 Learnings (2)
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to **/*.{ts,tsx} : Never swallow errors silently - at minimum log them with context

Applied to files:

  • apps/desktop/src/main/lib/terminal/port-manager.ts
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility

Applied to files:

  • apps/desktop/src/main/lib/terminal/port-scanner.ts
🧬 Code graph analysis (1)
apps/desktop/src/main/lib/terminal/port-scanner.test.ts (1)
apps/desktop/src/main/lib/terminal/port-scanner.ts (1)
  • PortInfo (5-10)
⏰ 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). (6)
  • GitHub Check: Deploy Docs
  • GitHub Check: Deploy API
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy Web
  • GitHub Check: Build
🔇 Additional comments (1)
apps/desktop/src/main/lib/terminal/port-scanner.ts (1)

82-82: Previously reported parsing bug has been fixed.

The code now correctly uses columns[columns.length - 2] to extract the NAME column (e.g., *:3000) before the (LISTEN) state marker. The tests in port-scanner.test.ts validate this fix extensively.

Comment on lines +68 to +71
const timeout = setTimeout(() => {
this.pendingHintScans.delete(paneId);
this.scanPane(paneId).catch(() => {});
}, HINT_SCAN_DELAY_MS);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid silently swallowing errors.

The empty .catch(() => {}) swallows any scan errors without logging. Per coding guidelines, errors should at minimum be logged with context.

🔎 Proposed fix
 		const timeout = setTimeout(() => {
 			this.pendingHintScans.delete(paneId);
-			this.scanPane(paneId).catch(() => {});
+			this.scanPane(paneId).catch((error) => {
+				console.error(`[PortManager] Hint-triggered scan failed for ${paneId}:`, error);
+			});
 		}, HINT_SCAN_DELAY_MS);
📝 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
const timeout = setTimeout(() => {
this.pendingHintScans.delete(paneId);
this.scanPane(paneId).catch(() => {});
}, HINT_SCAN_DELAY_MS);
const timeout = setTimeout(() => {
this.pendingHintScans.delete(paneId);
this.scanPane(paneId).catch((error) => {
console.error(`[PortManager] Hint-triggered scan failed for ${paneId}:`, error);
});
}, HINT_SCAN_DELAY_MS);
🤖 Prompt for AI Agents
In @apps/desktop/src/main/lib/terminal/port-manager.ts around lines 68 - 71,
Replace the silent .catch(() => {}) on the this.scanPane(paneId) promise with an
error-logging handler that records the error and context instead of swallowing
it; specifically, in the timeout callback where timeout is set and
this.pendingHintScans.delete(paneId) is called, change the catch to something
like .catch(err => { /* use existing logger (e.g. processLogger.error) or
console.error */ }) and include paneId and that the failure came from a
scheduled hint scan (HINT_SCAN_DELAY_MS) in the log message so the error is
observable while preserving non-throwing behavior.

Comment on lines +145 to 153
try {
const pid = session.pty.pid;
const pids = await getProcessTree(pid);
if (pids.length > 0) {
panePortMap.set(paneId, { workspaceId, pids });
}
} catch {
// Session may have exited
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Log errors in catch block for session scan failures.

The empty catch block at lines 151-152 silently ignores errors when getting the process tree for a session. While the comment indicates this is expected ("Session may have exited"), logging would help with debugging unexpected failures. Per coding guidelines, errors should not be swallowed silently.

🔎 Proposed fix
 				try {
 					const pid = session.pty.pid;
 					const pids = await getProcessTree(pid);
 					if (pids.length > 0) {
 						panePortMap.set(paneId, { workspaceId, pids });
 					}
-				} catch {
-					// Session may have exited
+				} catch (error) {
+					// Session may have exited - log for debugging
+					console.debug(`[PortManager] Failed to get process tree for ${paneId}:`, error);
 				}
📝 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
try {
const pid = session.pty.pid;
const pids = await getProcessTree(pid);
if (pids.length > 0) {
panePortMap.set(paneId, { workspaceId, pids });
}
} catch {
// Session may have exited
}
try {
const pid = session.pty.pid;
const pids = await getProcessTree(pid);
if (pids.length > 0) {
panePortMap.set(paneId, { workspaceId, pids });
}
} catch (error) {
// Session may have exited - log for debugging
console.debug(`[PortManager] Failed to get process tree for ${paneId}:`, error);
}
🤖 Prompt for AI Agents
In @apps/desktop/src/main/lib/terminal/port-manager.ts around lines 145 - 153,
The catch block swallowing errors around the process-tree scan should log the
error with context instead of being empty; update the try/catch around
getProcessTree(pid) (where pid is taken from session.pty.pid) to catch (err) and
call the module logger (or processLogger) to record a clear message including
paneId, workspaceId and the pid and the error object so unexpected failures are
visible while preserving the existing comment about sessions exiting.

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.

1 participant