From a3d46e471e3d4c85a60039a15553dc4b56f52dbc Mon Sep 17 00:00:00 2001 From: Francesco Giancane Date: Tue, 24 Feb 2026 01:41:59 +0000 Subject: [PATCH 1/9] Always quote Python executable path On Windows, this would cause pahts like "C:\Program Files\" to fail launching the Python command as the blank space was not escaped properly. Add also a unit test to validate this change. Signed-off-by: Francesco Giancane --- src/extension/debugger/adapter/factory.ts | 8 +++++++- src/test/unittest/adapter/factory.unit.test.ts | 18 +++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/extension/debugger/adapter/factory.ts b/src/extension/debugger/adapter/factory.ts index 5befc986..2a8090ed 100644 --- a/src/extension/debugger/adapter/factory.ts +++ b/src/extension/debugger/adapter/factory.ts @@ -80,6 +80,13 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac let executable = command.shift() ?? 'python'; + // Always ensure interpreter/command is quoted if necessary. Previously this was + // only done in the debugAdapterPath branch which meant that in the common case + // (using the built‑in adapter path) an interpreter path containing spaces would + // be passed unquoted, resulting in a fork/spawn failure on Windows. See bug + // report for details. + executable = fileToCommandArgumentForPythonExt(executable); + // "logToFile" is not handled directly by the adapter - instead, we need to pass // the corresponding CLI switch when spawning it. const logArgs = configuration.logToFile ? ['--log-dir', EXTENSION_ROOT_DIR] : []; @@ -87,7 +94,6 @@ export class DebugAdapterDescriptorFactory implements IDebugAdapterDescriptorFac if (configuration.debugAdapterPath !== undefined) { const args = command.concat([configuration.debugAdapterPath, ...logArgs]); traceLog(`DAP Server launched with command: ${executable} ${args.join(' ')}`); - executable = fileToCommandArgumentForPythonExt(executable); return new DebugAdapterExecutable(executable, args); } diff --git a/src/test/unittest/adapter/factory.unit.test.ts b/src/test/unittest/adapter/factory.unit.test.ts index 029ac48a..9c3a9e91 100644 --- a/src/test/unittest/adapter/factory.unit.test.ts +++ b/src/test/unittest/adapter/factory.unit.test.ts @@ -304,7 +304,23 @@ suite('Debugging - Adapter Factory', () => { assert.deepStrictEqual(descriptor, debugExecutable); }); - test('Add quotes to interpreter path with spaces', async () => { + test('Add quotes to interpreter path with spaces (default adapter path)', async () => { + const session = createSession({}); + const interpreterPathSpaces = 'path/to/python interpreter with spaces'; + const interpreterPathSpacesQuoted = `"${interpreterPathSpaces}"`; + const debugExecutable = new DebugAdapterExecutable(interpreterPathSpacesQuoted, [debugAdapterPath]); + + getInterpreterDetailsStub.resolves({ path: [interpreterPathSpaces] }); + const interpreterSpacePath: PythonEnvironment = createInterpreter(interpreterPathSpaces, '3.7.4-test'); + // Add architecture for completeness. + (interpreterSpacePath as any).architecture = Architecture.Unknown; + resolveEnvironmentStub.withArgs(interpreterPathSpaces).resolves(interpreterSpacePath); + const descriptor = await factory.createDebugAdapterDescriptor(session, nodeExecutable); + + assert.deepStrictEqual(descriptor, debugExecutable); + }); + + test('Add quotes to interpreter path with spaces when debugAdapterPath is specified', async () => { const customAdapterPath = 'custom/debug/adapter/customAdapterPath'; const session = createSession({ debugAdapterPath: customAdapterPath }); const interpreterPathSpaces = 'path/to/python interpreter with spaces'; From 3668184f171f5b2245f17bf86214234c1ca5758e Mon Sep 17 00:00:00 2001 From: Francesco Giancane Date: Wed, 25 Feb 2026 00:17:21 +0000 Subject: [PATCH 2/9] stringUtils.ts: remove call to .replace() method for paths There is no need anymore to replace backslashes with forward slashes for paths on Windows, as the Python extension's `toCommandArgumentForPythonExt` function already handles this correctly. Signed-off-by: Francesco Giancane --- src/extension/common/stringUtils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/extension/common/stringUtils.ts b/src/extension/common/stringUtils.ts index d5ce9742..29d80ebc 100644 --- a/src/extension/common/stringUtils.ts +++ b/src/extension/common/stringUtils.ts @@ -58,5 +58,8 @@ export function fileToCommandArgumentForPythonExt(source: string): string { if (!source) { return source; } - return toCommandArgumentForPythonExt(source).replace(/\\/g, '/'); + + let result = toCommandArgumentForPythonExt(source); + + return result; } From ef13e4535e0ea948f7bf407449fb9238ff0bee32 Mon Sep 17 00:00:00 2001 From: Francesco Giancane Date: Wed, 25 Feb 2026 00:29:15 +0000 Subject: [PATCH 3/9] stringUtils.ts: restore replace call when normalizing string --- src/extension/common/stringUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension/common/stringUtils.ts b/src/extension/common/stringUtils.ts index 29d80ebc..7f489376 100644 --- a/src/extension/common/stringUtils.ts +++ b/src/extension/common/stringUtils.ts @@ -61,5 +61,5 @@ export function fileToCommandArgumentForPythonExt(source: string): string { let result = toCommandArgumentForPythonExt(source); - return result; + return result.replace(/\\/g, '/'); } From 32822e04ed6a5199eaaa4d1aa6ba956a7f93708a Mon Sep 17 00:00:00 2001 From: Francesco Giancane Date: Wed, 25 Feb 2026 00:47:08 +0000 Subject: [PATCH 4/9] remoteLaunchers: ensure test is normalizing paths Explicitly call `fileToCommandArgumentForPythonExt` when computing a path with spaces and check results are as expected. --- src/test/unittest/adapter/remoteLaunchers.unit.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/unittest/adapter/remoteLaunchers.unit.test.ts b/src/test/unittest/adapter/remoteLaunchers.unit.test.ts index f681ee08..ca5c2964 100644 --- a/src/test/unittest/adapter/remoteLaunchers.unit.test.ts +++ b/src/test/unittest/adapter/remoteLaunchers.unit.test.ts @@ -8,6 +8,7 @@ import * as path from 'path'; import { EXTENSION_ROOT_DIR } from '../../../extension/common/constants'; import '../../../extension/common/promiseUtils'; import * as launchers from '../../../extension/debugger/adapter/remoteLaunchers'; +import { fileToCommandArgumentForPythonExt } from '../../../extension/common/stringUtils'; suite('External debugpy Debugger Launcher', () => { [ @@ -18,7 +19,7 @@ suite('External debugpy Debugger Launcher', () => { }, { testName: 'When path to debugpy contains spaces', - path: path.join('path', 'to', 'debugpy', 'with spaces'), + path: fileToCommandArgumentForPythonExt(path.join('path', 'to', 'debugpy', 'with spaces')), expectedPath: '"path/to/debugpy/with spaces"', }, ].forEach((testParams) => { From 745998a81bcba9bf39328f70d527be7e3f8066d8 Mon Sep 17 00:00:00 2001 From: Francesco Giancane Date: Wed, 25 Feb 2026 01:05:20 +0000 Subject: [PATCH 5/9] stringUtils.ts: replace backslashes with forward-slashes only on non-Windows platforms. --- src/extension/common/stringUtils.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/extension/common/stringUtils.ts b/src/extension/common/stringUtils.ts index 7f489376..fd9fcc6c 100644 --- a/src/extension/common/stringUtils.ts +++ b/src/extension/common/stringUtils.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { getOSType, OSType } from './platform'; + /** * Replaces all instances of a substring with a new substring. */ @@ -61,5 +63,9 @@ export function fileToCommandArgumentForPythonExt(source: string): string { let result = toCommandArgumentForPythonExt(source); - return result.replace(/\\/g, '/'); + if (getOSType() !== OSType.Windows) { + result = result.replace(/\\/g, '/'); + } + + return result; } From 6df15dc62320e05d6794cdc514bc3a149c32446e Mon Sep 17 00:00:00 2001 From: Francesco Giancane Date: Wed, 25 Feb 2026 01:06:20 +0000 Subject: [PATCH 6/9] Revert "remoteLaunchers: ensure test is normalizing paths" This reverts commit 32822e04ed6a5199eaaa4d1aa6ba956a7f93708a. --- src/test/unittest/adapter/remoteLaunchers.unit.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/unittest/adapter/remoteLaunchers.unit.test.ts b/src/test/unittest/adapter/remoteLaunchers.unit.test.ts index ca5c2964..f681ee08 100644 --- a/src/test/unittest/adapter/remoteLaunchers.unit.test.ts +++ b/src/test/unittest/adapter/remoteLaunchers.unit.test.ts @@ -8,7 +8,6 @@ import * as path from 'path'; import { EXTENSION_ROOT_DIR } from '../../../extension/common/constants'; import '../../../extension/common/promiseUtils'; import * as launchers from '../../../extension/debugger/adapter/remoteLaunchers'; -import { fileToCommandArgumentForPythonExt } from '../../../extension/common/stringUtils'; suite('External debugpy Debugger Launcher', () => { [ @@ -19,7 +18,7 @@ suite('External debugpy Debugger Launcher', () => { }, { testName: 'When path to debugpy contains spaces', - path: fileToCommandArgumentForPythonExt(path.join('path', 'to', 'debugpy', 'with spaces')), + path: path.join('path', 'to', 'debugpy', 'with spaces'), expectedPath: '"path/to/debugpy/with spaces"', }, ].forEach((testParams) => { From a0927d15e621ae840548b6b0f53776f8216d0747 Mon Sep 17 00:00:00 2001 From: Francesco Giancane Date: Wed, 25 Feb 2026 01:43:55 +0000 Subject: [PATCH 7/9] remoteLaunchers.ts: feed platform-specific data to tests Use backward slashes on Windows and forward slashes on Unix-like OSes when testing correct behavior of path strings quotation. --- .../adapter/remoteLaunchers.unit.test.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/test/unittest/adapter/remoteLaunchers.unit.test.ts b/src/test/unittest/adapter/remoteLaunchers.unit.test.ts index f681ee08..fc044221 100644 --- a/src/test/unittest/adapter/remoteLaunchers.unit.test.ts +++ b/src/test/unittest/adapter/remoteLaunchers.unit.test.ts @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. +// Licensed under the// MIT License. 'use strict'; @@ -8,18 +8,29 @@ import * as path from 'path'; import { EXTENSION_ROOT_DIR } from '../../../extension/common/constants'; import '../../../extension/common/promiseUtils'; import * as launchers from '../../../extension/debugger/adapter/remoteLaunchers'; +import { getOSType, OSType } from '../../../extension/common/platform'; + +function osExpectedPath(windowsPath: string, unixPath: string): string { + return getOSType() === OSType.Windows ? windowsPath : unixPath; +} suite('External debugpy Debugger Launcher', () => { [ { testName: 'When path to debugpy does not contains spaces', path: path.join('path', 'to', 'debugpy'), - expectedPath: 'path/to/debugpy', + expectedPath: osExpectedPath( + 'path\\to\\debugpy', + 'path/to/debugpy' + ), }, { testName: 'When path to debugpy contains spaces', path: path.join('path', 'to', 'debugpy', 'with spaces'), - expectedPath: '"path/to/debugpy/with spaces"', + expectedPath: osExpectedPath( + '"path\\to\\debugpy\\with spaces"', + '"path/to/debugpy/with spaces"' + ), }, ].forEach((testParams) => { suite(testParams.testName, async () => { From 2601ca59e412cbfe0ad064a70daa352199b84965 Mon Sep 17 00:00:00 2001 From: Francesco Giancane Date: Wed, 25 Feb 2026 01:48:56 +0000 Subject: [PATCH 8/9] Copy-paste error in comment. --- src/test/unittest/adapter/remoteLaunchers.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/unittest/adapter/remoteLaunchers.unit.test.ts b/src/test/unittest/adapter/remoteLaunchers.unit.test.ts index fc044221..a5677a9d 100644 --- a/src/test/unittest/adapter/remoteLaunchers.unit.test.ts +++ b/src/test/unittest/adapter/remoteLaunchers.unit.test.ts @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the// MIT License. +// Licensed under the MIT License. 'use strict'; From f4c64e5da3059040785b2b2592ab84bc423bd5a8 Mon Sep 17 00:00:00 2001 From: "Giancane, Francesco" Date: Wed, 25 Feb 2026 17:40:30 +0000 Subject: [PATCH 9/9] remoteLaunchers.unit.test.ts: fix formatting issues --- src/test/unittest/adapter/remoteLaunchers.unit.test.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/test/unittest/adapter/remoteLaunchers.unit.test.ts b/src/test/unittest/adapter/remoteLaunchers.unit.test.ts index a5677a9d..17e3ff17 100644 --- a/src/test/unittest/adapter/remoteLaunchers.unit.test.ts +++ b/src/test/unittest/adapter/remoteLaunchers.unit.test.ts @@ -19,18 +19,12 @@ suite('External debugpy Debugger Launcher', () => { { testName: 'When path to debugpy does not contains spaces', path: path.join('path', 'to', 'debugpy'), - expectedPath: osExpectedPath( - 'path\\to\\debugpy', - 'path/to/debugpy' - ), + expectedPath: osExpectedPath('path\\to\\debugpy', 'path/to/debugpy'), }, { testName: 'When path to debugpy contains spaces', path: path.join('path', 'to', 'debugpy', 'with spaces'), - expectedPath: osExpectedPath( - '"path\\to\\debugpy\\with spaces"', - '"path/to/debugpy/with spaces"' - ), + expectedPath: osExpectedPath('"path\\to\\debugpy\\with spaces"', '"path/to/debugpy/with spaces"'), }, ].forEach((testParams) => { suite(testParams.testName, async () => {