From 32e057bf060bdaf3e763c01d8bab4dbbb034c16b Mon Sep 17 00:00:00 2001 From: ZA139 <40553487+ZA139@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:53:40 +0800 Subject: [PATCH] Stop quoting interpreter path in adapter factory Remove shell-style quoting of the interpreter executable when constructing DebugAdapterExecutable and add a comment explaining why (child_process.spawn is invoked without a shell and manual quotes become part of the filename causing ENOENT). Drop the fileToCommandArgumentForPythonExt import and update unit tests to expect unquoted interpreter paths for both default and custom debug adapter paths. References regressions reported in #1013 and analysis of #964. --- src/extension/debugger/adapter/factory.ts | 14 ++++++-------- src/test/unittest/adapter/factory.unit.test.ts | 12 ++++-------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/extension/debugger/adapter/factory.ts b/src/extension/debugger/adapter/factory.ts index 2a8090ed..0e1a4b82 100644 --- a/src/extension/debugger/adapter/factory.ts +++ b/src/extension/debugger/adapter/factory.ts @@ -22,7 +22,6 @@ import { sendTelemetryEvent } from '../../telemetry'; import { Commands, EXTENSION_ROOT_DIR } from '../../common/constants'; import { Common, DebugConfigStrings, Interpreters } from '../../common/utils/localize'; import { IPersistentStateFactory } from '../../common/types'; -import { fileToCommandArgumentForPythonExt } from '../../common/stringUtils'; import { PythonEnvironment } from '../../envExtApi'; import { resolveEnvironment, getInterpreterDetails, runPythonExtensionCommand } from '../../common/python'; @@ -79,13 +78,12 @@ 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); + // DO NOT apply shell-style quoting here. + // The 'executable' path is passed to 'DebugAdapterExecutable', which internally + // uses 'child_process.spawn' in a non-shell environment. + // Manual quoting will cause the OS (especially Windows) to treat the quotes + // as part of the filename, leading to ENOENT. + // See regression reported in #1013 and analysis of #964. // "logToFile" is not handled directly by the adapter - instead, we need to pass // the corresponding CLI switch when spawning it. diff --git a/src/test/unittest/adapter/factory.unit.test.ts b/src/test/unittest/adapter/factory.unit.test.ts index 9c3a9e91..b68f7c25 100644 --- a/src/test/unittest/adapter/factory.unit.test.ts +++ b/src/test/unittest/adapter/factory.unit.test.ts @@ -304,28 +304,24 @@ suite('Debugging - Adapter Factory', () => { assert.deepStrictEqual(descriptor, debugExecutable); }); - test('Add quotes to interpreter path with spaces (default adapter path)', async () => { + test('Should not 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]); - + const debugExecutable = new DebugAdapterExecutable(interpreterPathSpaces, [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 () => { + test('Should not 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'; - const interpreterPathSpacesQuoted = `"${interpreterPathSpaces}"`; - const debugExecutable = new DebugAdapterExecutable(interpreterPathSpacesQuoted, [customAdapterPath]); + const debugExecutable = new DebugAdapterExecutable(interpreterPathSpaces, [customAdapterPath]); getInterpreterDetailsStub.resolves({ path: [interpreterPathSpaces] }); const interpreterSpacePath: PythonEnvironment = createInterpreter(interpreterPathSpaces, '3.7.4-test');