From b6e058e5b1129c3491972be72c1f1d4372f0a07f Mon Sep 17 00:00:00 2001 From: Akash Date: Sun, 23 Aug 2026 20:05:47 +0530 Subject: [PATCH 1/4] Fix Bash path interpretation for Windows terminal Fixed path interpretation issues for Bash commands in Windows, ensuring proper command and argument formatting. --- src/client/common/terminal/service.ts | 51 ++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index 0dffd5615ae1..e7366df2ceef 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// bug fix - Bash-VS Code path errors +import * as vscode from 'vscode'; import { inject, injectable } from 'inversify'; import { CancellationToken, Disposable, Event, EventEmitter, Terminal, TerminalShellExecution } from 'vscode'; import '../../common/extensions'; @@ -67,15 +69,56 @@ export class TerminalService implements ITerminalService, Disposable { }); } } - public async sendCommand(command: string, args: string[], _?: CancellationToken): Promise { - await this.ensureTerminal(); - const text = this.terminalHelper.buildCommandForTerminal(this.terminalShellType, command, args); + + // fixed Path interpretation bug between Bash and VS Code + // fixed Path interpretation bug between Bash and VS Code + public async sendCommand(command: string, args: string[] = []): Promise { + await this.ensureTerminal(); // <-- ADD THIS: Ensures terminal is booted up + if (!this.options?.hideFromUser) { this.terminal!.show(true); } - await this.executeCommand(text, false); + // Fetch the terminal settings from VS Code + const terminalSettings = vscode.workspace.getConfiguration('terminal.integrated'); + const defaultProfile = terminalSettings.get('defaultProfile.windows') || ''; + + // Check if the destination target is a Bash terminal + const isBashShell = defaultProfile.toLowerCase().includes('bash') || + command.toLowerCase().includes('bash.exe'); + + let processedCommand = command; + let processedArgs = [...args]; + + // If running on Windows but targeting Git Bash, swap backslashes to forward slashes! + if (process.platform === 'win32' && isBashShell) { + // Fix the executable binary path + processedCommand = processedCommand.replace(/\\/g, '/'); + processedCommand = processedCommand.replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`); + + // Fix the script file paths being sent as arguments + processedArgs = processedArgs.map(arg => { + let safeArg = arg.replace(/\\/g, '/'); + safeArg = safeArg.replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`); + return safeArg; + }); + } + + // Standard VS Code logic to stitch the command and arguments together + const text = processedArgs.reduce((p, c) => `${p} "${c}"`, processedCommand); + + // Ship the cleanly escaped string to the terminal stream! + this.terminal!.sendText(text, true); } + + + // Standard VS Code logic to stitch the command and arguments together + const text = processedArgs.reduce((p, c) => `${p} "${c}"`, processedCommand); + + // Ship the cleanly escaped string to the terminal stream! + this.terminal!.sendText(text, true); +} + /** @deprecated */ public async sendText(text: string): Promise { await this.ensureTerminal(); From 753eff15f9e3561674266c36cca47d6f29c7d155 Mon Sep 17 00:00:00 2001 From: Akash Date: Sun, 30 Aug 2026 11:26:41 +0530 Subject: [PATCH 2/4] Remove dupes Removed deprecated logic for stitching command and arguments in terminal service. --- src/client/common/terminal/service.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index e7366df2ceef..41219a40f705 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -112,12 +112,6 @@ export class TerminalService implements ITerminalService, Disposable { } - // Standard VS Code logic to stitch the command and arguments together - const text = processedArgs.reduce((p, c) => `${p} "${c}"`, processedCommand); - - // Ship the cleanly escaped string to the terminal stream! - this.terminal!.sendText(text, true); -} /** @deprecated */ public async sendText(text: string): Promise { From 032008db97b6d9e15d4de6a675c41eb1d4327516 Mon Sep 17 00:00:00 2001 From: Akash Date: Sun, 30 Aug 2026 11:31:13 +0530 Subject: [PATCH 3/4] refactoring code as per reviewer's directions Refactor command processing for Bash compatibility and restore command execution logic. --- src/client/common/terminal/service.ts | 36 ++++++++++++--------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index 41219a40f705..811cab60fa62 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -69,15 +69,14 @@ export class TerminalService implements ITerminalService, Disposable { }); } } - - // fixed Path interpretation bug between Bash and VS Code + // fixed Path interpretation bug between Bash and VS Code + public async sendCommand(command: string, args: string[] = []): Promise { - await this.ensureTerminal(); // <-- ADD THIS: Ensures terminal is booted up + await this.ensureTerminal(); - if (!this.options?.hideFromUser) { - this.terminal!.show(true); - } + let processedCommand = command; + let processedArgs = [...args]; // Fetch the terminal settings from VS Code const terminalSettings = vscode.workspace.getConfiguration('terminal.integrated'); @@ -87,32 +86,29 @@ export class TerminalService implements ITerminalService, Disposable { const isBashShell = defaultProfile.toLowerCase().includes('bash') || command.toLowerCase().includes('bash.exe'); - let processedCommand = command; - let processedArgs = [...args]; - - // If running on Windows but targeting Git Bash, swap backslashes to forward slashes! + // If running on Windows but targeting Git Bash, swap backslashes to forward slashes for paths if (process.platform === 'win32' && isBashShell) { - // Fix the executable binary path processedCommand = processedCommand.replace(/\\/g, '/'); processedCommand = processedCommand.replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`); - // Fix the script file paths being sent as arguments processedArgs = processedArgs.map(arg => { - let safeArg = arg.replace(/\\/g, '/'); - safeArg = safeArg.replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`); - return safeArg; + if (/^[A-Za-z]:\\|\\/.test(arg)) { + let safeArg = arg.replace(/\\/g, '/'); + return safeArg.replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`); + } + return arg; }); } - // Standard VS Code logic to stitch the command and arguments together - const text = processedArgs.reduce((p, c) => `${p} "${c}"`, processedCommand); + // --- RESTORED LOGIC --- + // 1. Let VS Code safely escape the arguments based on the active shell + const commandLine = this.terminalHelper.buildCommandForTerminal(this.terminalShellType, processedCommand, processedArgs); - // Ship the cleanly escaped string to the terminal stream! - this.terminal!.sendText(text, true); + // 2. Execute via the proper shell-aware pipeline, not a raw sendText + await this.executeCommand(commandLine, false); } - /** @deprecated */ public async sendText(text: string): Promise { await this.ensureTerminal(); From f2d288244b42af68b688fbcd0e8bb056c85e8c80 Mon Sep 17 00:00:00 2001 From: Akash Date: Sun, 30 Aug 2026 11:34:56 +0530 Subject: [PATCH 4/4] implemented fixes to the logic, and removed duplicacies. Key Changes Applied: Preserved Architecture Pipeline: Routes processedCommand and processedArgs back through this.terminalHelper.buildCommandForTerminal(...) and this.executeCommand(...) to retain shell-aware escaping, shell integration capabilities, and cross-platform handling. Strict Path Operand Normalization: Avoids naive path regexes like /^[A-Za-z]:\\|\\/ which corrupt CLI flags, regex patterns, or inline snippets (e.g., -c, --option, or Python code with \n). Converts only explicit file paths (e.g., C:\path\to\file or .\path\to\file). Cleaned Class Hierarchy: Removed all dangling/duplicate outer-scope statements to maintain clean TypeScript compilation. --- src/client/common/terminal/service.ts | 47 +++++++++++++++++---------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index 811cab60fa62..23b4bac67ca0 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -// bug fix - Bash-VS Code path errors import * as vscode from 'vscode'; import { inject, injectable } from 'inversify'; import { CancellationToken, Disposable, Event, EventEmitter, Terminal, TerminalShellExecution } from 'vscode'; @@ -59,6 +58,7 @@ export class TerminalService implements ITerminalService, Disposable { this.terminalManager.onDidCloseTerminal(this.terminalCloseHandler, this, disposableRegistry); this.terminalActivator = this.serviceContainer.get(ITerminalActivator); } + public dispose() { this.terminal?.dispose(); this.disposeReplListener(); @@ -69,46 +69,54 @@ export class TerminalService implements ITerminalService, Disposable { }); } } - - // fixed Path interpretation bug between Bash and VS Code - + public async sendCommand(command: string, args: string[] = []): Promise { await this.ensureTerminal(); - + let processedCommand = command; let processedArgs = [...args]; - // Fetch the terminal settings from VS Code const terminalSettings = vscode.workspace.getConfiguration('terminal.integrated'); const defaultProfile = terminalSettings.get('defaultProfile.windows') || ''; - // Check if the destination target is a Bash terminal const isBashShell = defaultProfile.toLowerCase().includes('bash') || command.toLowerCase().includes('bash.exe'); - // If running on Windows but targeting Git Bash, swap backslashes to forward slashes for paths if (process.platform === 'win32' && isBashShell) { - processedCommand = processedCommand.replace(/\\/g, '/'); - processedCommand = processedCommand.replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`); + // Normalize the binary command path if it contains Windows-style path delimiters + if (processedCommand.includes('\\') || /^[A-Za-z]:/.test(processedCommand)) { + processedCommand = processedCommand.replace(/\\/g, '/'); + processedCommand = processedCommand.replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`); + } + + // Strictly normalize argument operands that represent path targets + processedArgs = processedArgs.map((arg) => { + // Skip options/flags, multi-line blocks, or inline code snippets + if (arg.startsWith('-') || arg.includes('\n') || arg.includes('import ')) { + return arg; + } - processedArgs = processedArgs.map(arg => { - if (/^[A-Za-z]:\\|\\/.test(arg)) { + // Target explicit absolute or relative Windows paths + if (/^[A-Za-z]:\\[^\n]*$/.test(arg) || /^\.\\[^\n]*$/.test(arg)) { let safeArg = arg.replace(/\\/g, '/'); return safeArg.replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`); } + return arg; }); } - // --- RESTORED LOGIC --- - // 1. Let VS Code safely escape the arguments based on the active shell - const commandLine = this.terminalHelper.buildCommandForTerminal(this.terminalShellType, processedCommand, processedArgs); - - // 2. Execute via the proper shell-aware pipeline, not a raw sendText + // Delegate command construction to the shell-aware terminal helper + const commandLine = this.terminalHelper.buildCommandForTerminal( + this.terminalShellType, + processedCommand, + processedArgs, + ); + + // Execute via the standard execution pipeline await this.executeCommand(commandLine, false); } - /** @deprecated */ public async sendText(text: string): Promise { await this.ensureTerminal(); @@ -117,6 +125,7 @@ export class TerminalService implements ITerminalService, Disposable { } this.terminal!.sendText(text); } + public async executeCommand( commandLine: string, isPythonShell: boolean, @@ -242,6 +251,7 @@ export class TerminalService implements ITerminalService, Disposable { this.terminal!.show(preserveFocus); } } + // TODO: Debt switch to Promise ---> breaks 20 tests public async ensureTerminal(preserveFocus: boolean = true): Promise { if (this.terminal) { @@ -279,6 +289,7 @@ export class TerminalService implements ITerminalService, Disposable { this.sendTelemetry().ignoreErrors(); return; } + private terminalCloseHandler(terminal: Terminal) { if (terminal === this.terminal) { this.terminalClosed.fire();