Skip to content
Open
4 changes: 2 additions & 2 deletions apps/desktop/scripts/copy-native-modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ function copyModuleIfSymlink(
console.log(` ${moduleName}: symlink -> replacing with real files`);
console.log(` Real path: ${realPath}`);

// Remove the symlink
rmSync(modulePath);
// Remove the symlink (recursive needed on Windows for junction/symlink dirs)
rmSync(modulePath, { recursive: true, force: true });
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 10, 2026

Choose a reason for hiding this comment

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

P2: Gate the Windows symlink/junction removal flags behind a runtime platform check instead of changing the shared path for all OSes.

(Based on your team's feedback about gating platform-specific changes with runtime checks.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/scripts/copy-native-modules.ts, line 110:

<comment>Gate the Windows symlink/junction removal flags behind a runtime platform check instead of changing the shared path for all OSes.

(Based on your team's feedback about gating platform-specific changes with runtime checks.) </comment>

<file context>
@@ -106,8 +106,8 @@ function copyModuleIfSymlink(
-		// Remove the symlink
-		rmSync(modulePath);
+		// Remove the symlink (recursive needed on Windows for junction/symlink dirs)
+		rmSync(modulePath, { recursive: true, force: true });
 
 		// Copy the actual files
</file context>
Suggested change
rmSync(modulePath, { recursive: true, force: true });
if (process.platform === "win32") {
rmSync(modulePath, { recursive: true, force: true });
} else {
rmSync(modulePath);
}
Fix with Cubic


// Copy the actual files
cpSync(realPath, modulePath, { recursive: true });
Expand Down
31 changes: 25 additions & 6 deletions apps/desktop/src/lib/trpc/routers/external/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const BUNDLE_ID_CANDIDATES: Partial<Record<ExternalApp, string[]>> = {
};

/** Map of app IDs to their Linux CLI commands */
const LINUX_CLI_COMMANDS: Record<ExternalApp, string | null> = {
const CLI_COMMANDS: Record<ExternalApp, string | null> = {
finder: null, // Handled specially with shell.showItemInFolder
vscode: "code",
"vscode-insiders": "code-insiders",
Expand Down Expand Up @@ -77,7 +77,7 @@ const LINUX_CLI_COMMANDS: Record<ExternalApp, string | null> = {
* JetBrains Toolbox typically creates `idea`/`pycharm` launchers,
* while package managers may use edition-specific names.
*/
const LINUX_CLI_CANDIDATES: Partial<Record<ExternalApp, string[]>> = {
const CLI_CANDIDATES: Partial<Record<ExternalApp, string[]>> = {
intellij: ["idea", "intellij-idea-ultimate", "intellij-idea-community"],
pycharm: ["pycharm", "pycharm-professional", "pycharm-community"],
};
Expand Down Expand Up @@ -109,16 +109,33 @@ export function getAppCommand(
return [{ command: "open", args: ["-a", appName, targetPath] }];
}

// Linux (and other non-macOS platforms)
const linuxCandidates = LINUX_CLI_CANDIDATES[app];
if (platform === "win32") {
if (app === "finder") {
return [{ command: "explorer.exe", args: [targetPath] }];
}

const winCandidates = CLI_CANDIDATES[app];
if (winCandidates) {
return winCandidates.map((cmd) => ({
command: cmd,
args: [targetPath],
}));
}

const winCommand = CLI_COMMANDS[app];
if (!winCommand) return null;
return [{ command: winCommand, args: [targetPath] }];
Comment on lines +117 to +127
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 10, 2026

Choose a reason for hiding this comment

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

P1: Windows app commands are returned as raw CLI names, but they are executed with spawn without a shell; common Windows CLI launchers (.cmd/.bat) can fail to start in this path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/lib/trpc/routers/external/helpers.ts, line 115:

<comment>Windows app commands are returned as raw CLI names, but they are executed with `spawn` without a shell; common Windows CLI launchers (`.cmd`/`.bat`) can fail to start in this path.</comment>

<file context>
@@ -107,16 +107,33 @@ export function getAppCommand(
+			return [{ command: "explorer.exe", args: [targetPath] }];
+		}
+
+		const winCandidates = CLI_CANDIDATES[app];
+		if (winCandidates) {
+			return winCandidates.map((cmd) => ({
</file context>
Suggested change
const winCandidates = CLI_CANDIDATES[app];
if (winCandidates) {
return winCandidates.map((cmd) => ({
command: cmd,
args: [targetPath],
}));
}
const winCommand = CLI_COMMANDS[app];
if (!winCommand) return null;
return [{ command: winCommand, args: [targetPath] }];
const winCandidates = CLI_CANDIDATES[app];
if (winCandidates) {
return winCandidates.map((cmd) => ({
command: "cmd.exe",
args: ["/d", "/s", "/c", cmd, targetPath],
}));
}
const winCommand = CLI_COMMANDS[app];
if (!winCommand) return null;
return [
{
command: "cmd.exe",
args: ["/d", "/s", "/c", winCommand, targetPath],
},
];
Fix with Cubic

}

const linuxCandidates = CLI_CANDIDATES[app];
if (linuxCandidates) {
return linuxCandidates.map((cmd) => ({
command: cmd,
args: [targetPath],
}));
}

const cliCommand = LINUX_CLI_COMMANDS[app];
const cliCommand = CLI_COMMANDS[app];
if (!cliCommand) return null;
return [{ command: cliCommand, args: [targetPath] }];
}
Expand Down Expand Up @@ -149,9 +166,11 @@ const TRAILING_PUNCTUATION = /[.,;:!?]+$/;
function looksLikePath(str: string): boolean {
return (
str.includes("/") ||
str.includes("\\") ||
str.startsWith(".") ||
str.startsWith("~") ||
str.startsWith("/")
str.startsWith("/") ||
/^[A-Za-z]:/.test(str)
Comment on lines +169 to +173
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

str.includes("\\") is too broad for looksLikePath().

extractEmbeddedPath() trusts this helper, so inputs like foo(\\d+)bar now get extracted and resolved as file paths just because they contain a backslash. Please tighten this to actual Windows path shapes (drive-letter, UNC, or a more filename-like backslash pattern).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/external/helpers.ts` around lines 167 -
171, The check str.includes("\\") in looksLikePath() is too permissive; update
looksLikePath to only treat Windows-style paths as true — specifically detect
drive-letter paths (like "C:\\"), UNC paths (starting with "\\\\"), or
backslash-based paths that show a directory separator followed by a filename
component (not just any backslash from e.g. a regex). Modify the logic in
looksLikePath() (and any callers such as extractEmbeddedPath()) to replace the
broad includes("\\") with stricter patterns for drive-letter, UNC, or a
backslash + name segment, and add/adjust unit tests to cover cases like
"foo(\\d+)bar", "C:\\path\\file.txt", "\\\\server\\share\\file", and
"dir\\file".

);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ interface GetShellEnvironmentOptions {
export async function getShellEnvironment(
options?: GetShellEnvironmentOptions,
): Promise<Record<string, string>> {
if (process.platform === "win32") {
return copyStringEnv();
}

const now = Date.now();
const ttl = isFallbackCache ? fallbackCacheTtlMs : CACHE_TTL_MS;
if (!options?.forceRefresh && cachedEnv && now - cacheTime < ttl) {
Expand Down
9 changes: 9 additions & 0 deletions apps/desktop/src/main/lib/agent-setup/shell-wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ export function getShellArgs(
if (["zsh", "sh", "ksh"].includes(shellName)) {
return ["-l"];
}
if (["powershell", "pwsh", "powershell.exe", "pwsh.exe"].includes(shellName)) {
return ["-NoLogo"];
}
return [];
}

Expand Down Expand Up @@ -370,5 +373,11 @@ export function getCommandShellArgs(
`source ${quoteShellLiteral(bashRcfile)} &&\n${commandWithManagedPrelude}`,
];
}
if (["powershell", "pwsh", "powershell.exe", "pwsh.exe"].includes(shellName)) {
return ["-NoLogo", "-Command", command];
}
if (["cmd", "cmd.exe"].includes(shellName)) {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 10, 2026

Choose a reason for hiding this comment

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

P2: Normalize cmd shell detection to handle Windows-style absolute paths. As written, C:\\...\\cmd.exe won’t match this branch and can fall through to the -lc fallback.

(Based on your team's feedback about using cross-platform path utilities instead of split.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/main/lib/agent-setup/shell-wrappers.ts, line 313:

<comment>Normalize cmd shell detection to handle Windows-style absolute paths. As written, `C:\\...\\cmd.exe` won’t match this branch and can fall through to the `-lc` fallback.

(Based on your team's feedback about using cross-platform path utilities instead of split.) </comment>

<file context>
@@ -304,5 +307,11 @@ export function getCommandShellArgs(
+	if (["powershell", "pwsh", "powershell.exe", "pwsh.exe"].includes(shellName)) {
+		return ["-NoLogo", "-Command", command];
+	}
+	if (["cmd", "cmd.exe"].includes(shellName)) {
+		return ["/C", command];
+	}
</file context>
Suggested change
if (["cmd", "cmd.exe"].includes(shellName)) {
if (/(^|[\\/])cmd(\.exe)?$/i.test(shell)) {
Fix with Cubic

return ["/C", command];
}
return ["-lc", commandWithManagedPrelude];
}
3 changes: 2 additions & 1 deletion apps/desktop/src/main/lib/auto-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ function isPrereleaseBuild(): boolean {
}

const IS_PRERELEASE = isPrereleaseBuild();
const IS_AUTO_UPDATE_PLATFORM = PLATFORM.IS_MAC || PLATFORM.IS_LINUX;
const IS_AUTO_UPDATE_PLATFORM =
PLATFORM.IS_MAC || PLATFORM.IS_LINUX || PLATFORM.IS_WINDOWS;
Comment on lines +24 to +25
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

Update the unsupported-platform copy to include Windows.

Line 24 now enables Windows here, but Line 135 still tells users that auto-updates are only available on macOS and Linux.

📝 Suggested fix
-			message: "Auto-updates are only available on macOS and Linux.",
+			message: "Auto-updates are only available on macOS, Linux, and Windows.",
📝 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 IS_AUTO_UPDATE_PLATFORM =
PLATFORM.IS_MAC || PLATFORM.IS_LINUX || PLATFORM.IS_WINDOWS;
message: "Auto-updates are only available on macOS, Linux, and Windows.",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/lib/auto-updater.ts` around lines 24 - 25, The
unsupported-platform user-facing string still says "auto-updates are only
available on macOS and Linux" even though IS_AUTO_UPDATE_PLATFORM (based on
PLATFORM.IS_MAC || PLATFORM.IS_LINUX || PLATFORM.IS_WINDOWS) now includes
Windows; update the copy in apps/desktop/src/main/lib/auto-updater.ts to mention
Windows as supported (or reword to "macOS, Linux, and Windows") wherever the
unsupported-platform message is defined so it matches the
IS_AUTO_UPDATE_PLATFORM logic (search for the string or the function that
returns the unsupported-platform message and update it).

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 10, 2026

Choose a reason for hiding this comment

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

P3: Update the unsupported-platform message to include Windows now that IS_AUTO_UPDATE_PLATFORM allows it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/main/lib/auto-updater.ts, line 25:

<comment>Update the unsupported-platform message to include Windows now that `IS_AUTO_UPDATE_PLATFORM` allows it.</comment>

<file context>
@@ -21,7 +21,8 @@ function isPrereleaseBuild(): boolean {
 const IS_PRERELEASE = isPrereleaseBuild();
-const IS_AUTO_UPDATE_PLATFORM = PLATFORM.IS_MAC || PLATFORM.IS_LINUX;
+const IS_AUTO_UPDATE_PLATFORM =
+	PLATFORM.IS_MAC || PLATFORM.IS_LINUX || PLATFORM.IS_WINDOWS;
 
 // Use explicit feed URLs to ensure we always fetch platform-specific manifests
</file context>
Fix with Cubic


// Use explicit feed URLs to ensure we always fetch platform-specific manifests
// (for example latest-mac.yml and latest-linux.yml) from the correct release.
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/main/lib/host-service-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export class HostServiceManager {

const child = childProcess.spawn(process.execPath, [this.scriptPath], {
stdio: ["ignore", "pipe", "pipe", "ipc"],
windowsHide: true,
env,
});
instance.process = child;
Expand Down
49 changes: 31 additions & 18 deletions apps/desktop/src/main/lib/terminal-host/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { homedir } from "node:os";
import { join } from "node:path";
import { app } from "electron";
import { SUPERSET_DIR_NAME } from "shared/constants";
import { getSocketPath, isFileBasedSocket, socketMayExist } from "./paths";
import { throwIfAborted } from "../terminal/abort";
import { TerminalAttachCanceledError } from "../terminal/errors";
import {
Expand Down Expand Up @@ -72,7 +73,7 @@ const DEBUG_CLIENT = process.env.SUPERSET_TERMINAL_DEBUG === "1";
// Get from shared constants for multi-worktree support (imported at top of file)
const SUPERSET_HOME_DIR = join(homedir(), SUPERSET_DIR_NAME);

const SOCKET_PATH = join(SUPERSET_HOME_DIR, "terminal-host.sock");
const SOCKET_PATH = getSocketPath(SUPERSET_DIR_NAME, SUPERSET_HOME_DIR);
const TOKEN_PATH = join(SUPERSET_HOME_DIR, "terminal-host.token");
const PID_PATH = join(SUPERSET_HOME_DIR, "terminal-host.pid");
const SPAWN_LOCK_PATH = join(SUPERSET_HOME_DIR, "terminal-host.spawn.lock");
Expand Down Expand Up @@ -496,7 +497,7 @@ export class TerminalHostClient extends EventEmitter {

private async tryConnectControl(): Promise<boolean> {
return new Promise((resolve) => {
if (!existsSync(SOCKET_PATH)) {
if (!socketMayExist(SOCKET_PATH)) {
resolve(false);
return;
}
Expand Down Expand Up @@ -544,7 +545,7 @@ export class TerminalHostClient extends EventEmitter {

private async tryConnectStream(): Promise<boolean> {
return new Promise((resolve) => {
if (!existsSync(SOCKET_PATH)) {
if (!socketMayExist(SOCKET_PATH)) {
resolve(false);
return;
}
Expand Down Expand Up @@ -898,7 +899,7 @@ export class TerminalHostClient extends EventEmitter {
}: {
killSessions?: boolean;
} = {}): Promise<void> {
if (!existsSync(SOCKET_PATH)) return;
if (!socketMayExist(SOCKET_PATH)) return;

const token = this.readAuthToken();

Expand Down Expand Up @@ -993,7 +994,7 @@ export class TerminalHostClient extends EventEmitter {
const timeoutMs = 2000;

while (Date.now() - startTime < timeoutMs) {
if (!existsSync(SOCKET_PATH)) return;
if (!socketMayExist(SOCKET_PATH)) return;
const live = await this.isSocketLive();
if (!live) return;
await this.sleep(100);
Expand All @@ -1010,7 +1011,7 @@ export class TerminalHostClient extends EventEmitter {
*/
private isSocketLive(): Promise<boolean> {
return new Promise((resolve) => {
if (!existsSync(SOCKET_PATH)) {
if (!socketMayExist(SOCKET_PATH)) {
resolve(false);
return;
}
Expand Down Expand Up @@ -1092,7 +1093,9 @@ export class TerminalHostClient extends EventEmitter {
private async spawnDaemon(): Promise<void> {
// Check if socket is live first - this is the authoritative check
// PID file can be stale if daemon crashed and PID was reused by another process
if (existsSync(SOCKET_PATH)) {
const socketFileExists =
socketMayExist(SOCKET_PATH);
if (socketFileExists) {
const isLive = await this.isSocketLive();
if (isLive) {
if (DEBUG_CLIENT) {
Expand All @@ -1102,13 +1105,16 @@ export class TerminalHostClient extends EventEmitter {
}

// Socket exists but not responsive - safe to remove
if (DEBUG_CLIENT) {
console.log("[TerminalHostClient] Removing stale socket file");
}
try {
unlinkSync(SOCKET_PATH);
} catch {
// Ignore - might not have permission
// On Windows, named pipes are managed by the OS and don't need manual cleanup
if (isFileBasedSocket()) {
if (DEBUG_CLIENT) {
console.log("[TerminalHostClient] Removing stale socket file");
}
try {
unlinkSync(SOCKET_PATH);
} catch {
// Ignore - might not have permission
}
}
}

Expand Down Expand Up @@ -1190,6 +1196,7 @@ export class TerminalHostClient extends EventEmitter {
try {
child = spawn(process.execPath, [daemonScript], {
detached: true,
windowsHide: true,
stdio: logFd >= 0 ? ["ignore", logFd, logFd] : "ignore",
env: {
...process.env,
Expand Down Expand Up @@ -1260,10 +1267,16 @@ export class TerminalHostClient extends EventEmitter {
const startTime = Date.now();

while (Date.now() - startTime < SPAWN_WAIT_MS) {
if (existsSync(SOCKET_PATH)) {
// Give it a moment to start listening
await this.sleep(200);
return;
if (isFileBasedSocket()) {
// Unix: check if socket file appeared on disk
if (existsSync(SOCKET_PATH)) {
await this.sleep(200);
return;
}
} else {
// Windows: named pipes don't exist as files, try connecting
const live = await this.isSocketLive();
if (live) return;
}
await this.sleep(100);
}
Expand Down
36 changes: 36 additions & 0 deletions apps/desktop/src/main/lib/terminal-host/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import { PLATFORM } from "shared/constants";

/**
* Returns the IPC socket/pipe path for the terminal host daemon.
* On Unix, this is a Unix domain socket file inside the superset home directory.
* On Windows, this is a named pipe (named pipes don't exist as files on disk).
*/
export function getSocketPath(
supersetDirName: string,
supersetHomeDir: string,
): string {
if (PLATFORM.IS_WINDOWS) {
return `\\\\?\\pipe\\superset-terminal-host-${supersetDirName}`;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 10, 2026

Choose a reason for hiding this comment

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

P2: Windows named pipe name is not user-scoped, so different local users can collide on the same terminal-host pipe.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/main/lib/terminal-host/paths.ts, line 15:

<comment>Windows named pipe name is not user-scoped, so different local users can collide on the same terminal-host pipe.</comment>

<file context>
@@ -0,0 +1,36 @@
+	supersetHomeDir: string,
+): string {
+	if (PLATFORM.IS_WINDOWS) {
+		return `\\\\?\\pipe\\superset-terminal-host-${supersetDirName}`;
+	}
+	return join(supersetHomeDir, "terminal-host.sock");
</file context>
Fix with Cubic

}
return join(supersetHomeDir, "terminal-host.sock");
}

/**
* Whether the IPC transport uses a file-based socket (Unix domain socket)
* rather than a named pipe. Only file-based sockets can be checked with `existsSync`.
*/
export function isFileBasedSocket(): boolean {
return !PLATFORM.IS_WINDOWS;
}

/**
* Checks whether the IPC socket/pipe exists.
* On Unix, checks for the socket file on disk.
* On Windows, named pipes can't be checked via the filesystem, so assumes true.
*/
export function socketMayExist(socketPath: string): boolean {
if (!isFileBasedSocket()) return true;
return existsSync(socketPath);
}
2 changes: 1 addition & 1 deletion apps/desktop/src/main/lib/terminal/daemon/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const SESSION_CLEANUP_DELAY_MS = 5000;
export const DEBUG_TERMINAL = process.env.SUPERSET_TERMINAL_DEBUG === "1";
export const CREATE_OR_ATTACH_CONCURRENCY = 3;
export const MAX_SCROLLBACK_BYTES = 500_000;
export const MAX_SCROLLBACK_BYTES = 150_000;
export const MAX_HISTORY_SCROLLBACK_BYTES = 512 * 1024;
export const MAX_KILLED_SESSION_TOMBSTONES = 1000;
23 changes: 16 additions & 7 deletions apps/desktop/src/main/lib/terminal/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ let cachedMacosSystemCertAvailable: boolean | null = null;

function startLocaleProbe(): void {
if (cachedUtf8Locale || localeProbeInFlight) return;
// The `locale` command does not exist on Windows
if (os.platform() === "win32") {
cachedUtf8Locale = "en_US.UTF-8";
return;
}
localeProbeInFlight = true;

exec(
Expand All @@ -42,7 +47,8 @@ function startLocaleProbe(): void {
*/
export const HOOK_PROTOCOL_VERSION = "2";

export const FALLBACK_SHELL = os.platform() === "win32" ? "cmd.exe" : "/bin/sh";
export const FALLBACK_SHELL =
os.platform() === "win32" ? "powershell.exe" : "/bin/sh";
export const SHELL_CRASH_THRESHOLD_MS = 1000;

type DefaultShellModuleShape =
Expand Down Expand Up @@ -73,15 +79,18 @@ export function normalizeDefaultShell(
}

export function getDefaultShell(): string {
const resolvedDefaultShell = normalizeDefaultShell(defaultShell);
if (resolvedDefaultShell) {
return resolvedDefaultShell;
}

const platform = os.platform();

// On Windows, always use PowerShell. The `default-shell` package returns
// COMSPEC (cmd.exe), which can't handle the subexpressions and file-read
// syntax used by agent launch commands.
if (platform === "win32") {
return process.env.COMSPEC || "powershell.exe";
return "powershell.exe";
}
Comment on lines +50 to +89
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

Keep the Windows fallback shell distinct from the primary shell.

In apps/desktop/src/main/lib/terminal/session.ts:98, useFallbackShell chooses between FALLBACK_SHELL and getDefaultShell(). With both now resolving to powershell.exe on Windows, the recovery path becomes a no-op: if PowerShell itself or its profile is what breaks startup, retrying with the fallback shell will just relaunch the same failing shell.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/lib/terminal/env.ts` around lines 42 - 54, The fallback
shell on Windows is currently identical to the primary shell (both resolve to
"powershell.exe"), so retrying with FALLBACK_SHELL in useFallbackShell becomes a
no-op; change FALLBACK_SHELL to a different Windows shell (for example "cmd.exe"
or the COMSPEC path) so that fallback attempts in session.ts (where
useFallbackShell compares FALLBACK_SHELL and getDefaultShell()) actually launch
a different binary; update the FALLBACK_SHELL declaration (and any comments) to
return a distinct Windows fallback while leaving getDefaultShell() returning
"powershell.exe".


const resolvedDefaultShell = normalizeDefaultShell(defaultShell);
if (resolvedDefaultShell) {
return resolvedDefaultShell;
}

if (process.env.SHELL) {
Expand Down
5 changes: 3 additions & 2 deletions apps/desktop/src/main/lib/terminal/port-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ async function getListeningPortsWindows(pids: number[]): Promise<PortInfo[]> {
const { stdout: output } = await execAsync("netstat -ano", {
maxBuffer: 10 * 1024 * 1024,
timeout: EXEC_TIMEOUT_MS,
windowsHide: true,
});

const pidSet = new Set(pids);
Expand Down Expand Up @@ -199,7 +200,7 @@ async function getProcessNameWindows(pid: number): Promise<string> {
try {
const { stdout: output } = await execAsync(
`wmic process where processid=${pid} get name 2>nul`,
{ timeout: EXEC_TIMEOUT_MS },
{ timeout: EXEC_TIMEOUT_MS, windowsHide: true },
);
const lines = output.trim().split("\n");
if (lines.length >= 2) {
Expand All @@ -211,7 +212,7 @@ async function getProcessNameWindows(pid: number): Promise<string> {
try {
const { stdout: output } = await execAsync(
`powershell -Command "(Get-Process -Id ${pid}).ProcessName"`,
{ timeout: EXEC_TIMEOUT_MS },
{ timeout: EXEC_TIMEOUT_MS, windowsHide: true },
);
return output.trim() || "unknown";
} catch {}
Expand Down
Loading