From 152b71f5e0bbdc5145abb5da43e3f3637eb99b90 Mon Sep 17 00:00:00 2001 From: Andrew Barnes <169967362+Bortlesboat@users.noreply.github.com> Date: Sun, 8 Nov 2026 09:57:58 -0500 Subject: [PATCH] fix(debugger): debug Python with python, not python3, on Windows (#2159) Debugging a Python configuration with no interpreter path (a global `pytest`, `uv run`, `poetry run`) set debugpy's adapter Python to a hardcoded `python3`. On Windows that is at most a Store alias, and the debugpy venv the IDE installs has only `python.exe`, so the managed debugpy was skipped. The launch now uses `python` on Windows, based on the host OS so remote sessions follow the server. Other platforms are unchanged. Sibling covered: `debugLauncherPython` uses the same fallback. Co-authored-by: Bortlesboat <169967362+Bortlesboat@users.noreply.github.com> --- .../debugger/__tests__/launch.test.ts | 19 ++++++++++++++++++- src/features/debugger/launch.ts | 10 ++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/features/debugger/__tests__/launch.test.ts b/src/features/debugger/__tests__/launch.test.ts index 191b3dba8..359664287 100644 --- a/src/features/debugger/__tests__/launch.test.ts +++ b/src/features/debugger/__tests__/launch.test.ts @@ -1,5 +1,6 @@ -import { describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; import type { RunConfig } from "@/platform/ipc"; +import { usePlatform } from "@/platform/platformInfo"; import { debugUnsupportedReason, launchConfigFor } from "../launch"; const rc = (p: Partial): RunConfig => ({ @@ -18,6 +19,10 @@ const rc = (p: Partial): RunConfig => ({ const extra = (l: { extraJson: string | null } | null) => JSON.parse(l?.extraJson ?? "{}"); +const originalPlatform = usePlatform.getState(); +beforeEach(() => usePlatform.setState({ os: "linux" })); +afterEach(() => usePlatform.setState(originalPlatform)); + describe("run config → launch config", () => { it("returns null without an adapter", () => { expect(launchConfigFor(rc({}), "/p")).toBeNull(); @@ -136,6 +141,18 @@ describe("run config → launch config", () => { expect(extra(django)).toMatchObject({ django: true, python: ["pdm", "run", "python"] }); }); + it("debugpy: Windows falls back to python, not the python3 Store alias", () => { + usePlatform.setState({ os: "windows" }); + const globalPytest = launchConfigFor(rc({ kind: "pytest", command: "pytest", debugAdapter: "debugpy" }), "/p")!; + expect(extra(globalPytest).python).toBe("python"); + + const uv = launchConfigFor( + rc({ kind: "python", command: "uv", args: ["run", "python", "main.py"], debugAdapter: "debugpy" }), + "/p", + )!; + expect(extra(uv)).toMatchObject({ python: ["uv", "run", "python"], debugLauncherPython: "python" }); + }); + it("js-debug: npm scripts run through the package manager, node scripts directly", () => { const npm = launchConfigFor( rc({ kind: "npm", command: "pnpm", args: ["run", "dev"], cwd: "web", debugAdapter: "js-debug" }), diff --git a/src/features/debugger/launch.ts b/src/features/debugger/launch.ts index ac8a2c00d..1193277f5 100644 --- a/src/features/debugger/launch.ts +++ b/src/features/debugger/launch.ts @@ -2,6 +2,7 @@ import { parseArgs, formatArgs } from "@/platform/commandLine"; /** Run configuration → DAP launch configuration, per adapter. */ import { workflowSettings } from "@/platform/workflowSettings"; import type { LaunchConfig, RunConfig } from "@/platform/ipc"; +import { hostOs } from "@/platform/remote"; function isAbsolute(p: string) { return p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p); @@ -60,13 +61,18 @@ function cargoLaunch(rc: RunConfig, cwd: string, adapter: string): LaunchConfig const PYTHON_NAME = /^python[\d.]*(\.exe)?$/; +/** `python` on Windows (`python3` is at most a Store alias there, and venvs have no `python3.exe`), `python3` elsewhere. */ +function systemPython(): string { + return hostOs() === "windows" ? "python" : "python3"; +} + function pythonFor(rc: RunConfig, cwd: string): string { const cmd = rc.command; const name = cmd.split("/").pop() ?? cmd; if (PYTHON_NAME.test(name)) return resolveCommand(cmd, cwd); // A venv tool such as `.venv/bin/pytest`: use the interpreter next to it. if (cmd.includes("/")) return resolveCommand(cmd.slice(0, cmd.lastIndexOf("/") + 1) + "python", cwd); - return "python3"; + return systemPython(); } /** Project managers whose `run` executes in the project's environment (`uv run python …`). */ @@ -91,7 +97,7 @@ function debugpyLaunch(rc: RunConfig, cwd: string): LaunchConfig { // environment; the adapter and its launcher need a plain interpreter. const python = PYTHON_NAME.test(args[1]) ? args[1] : "python"; extra.python = [resolveCommand(rc.command, cwd), "run", python]; - extra.debugLauncherPython = "python3"; + extra.debugLauncherPython = systemPython(); tool = PYTHON_NAME.test(args[1]) ? null : args[1]; args = args.slice(2); }