Skip to content

fix(debugpy): auto-detect active venv, add --python override#20

Merged
AlmogBaku merged 3 commits intomasterfrom
fix/python-venv-detection
Apr 17, 2026
Merged

fix(debugpy): auto-detect active venv, add --python override#20
AlmogBaku merged 3 commits intomasterfrom
fix/python-venv-detection

Conversation

@AlmogBaku
Copy link
Copy Markdown
Owner

Summary

  • dap debug script.py silently ignored the active virtualenv and always used system python3, causing wrong-interpreter errors (e.g. missing debugpy in the venv, Python version mismatch).
  • CLI now resolves $VIRTUAL_ENV/bin/python{3,} at invocation and passes the path to the daemon (whose env may be stale).
  • New --python flag overrides auto-detection for conda / pyenv shims / non-standard layouts.

Changes

  • debugpyBackend gains a Python field; Spawn honors it (falls back to python3 when unset).
  • DebugArgs.Python plumbs the CLI-resolved interpreter to the daemon.
  • ResolveVenvPython() helper reads $VIRTUAL_ENV at CLI call time.
  • Docs updated in references/installing-debuggers.md.

Test plan

  • Unit: ResolveVenvPython — no env, missing binary, python3, fallback to python, prefers python3.
  • Unit: debugpyBackend.Spawn uses configured Python binary.
  • E2E: --python happy path lands at breakpoint.
  • E2E: bogus --python fails cleanly (no silent fallback).
  • E2E: VIRTUAL_ENV auto-detection picks up stub venv python (proves auto-detect wins over system).
  • Full suite (go test ./...) passes.

🤖 Generated with Claude Code

`dap debug script.py` previously always spawned `python3` from PATH,
ignoring the activated virtualenv. This caused wrong-interpreter errors
(and missing imports like `debugpy` itself) when users had a venv
active.

- CLI resolves `$VIRTUAL_ENV/bin/python{3,}` at invocation time (the
  daemon's env may be stale), plumbed via new `DebugArgs.Python` field.
- New `--python` flag overrides auto-detection for conda/pyenv/shims.
- `debugpyBackend` gains `Python` field; `Spawn` honors it.
- Unit tests for `ResolveVenvPython` + `debugpyBackend.Python` wiring.
- E2E tests: explicit `--python`, bogus `--python` fails cleanly,
  `VIRTUAL_ENV` auto-detection end-to-end.
- Docs: venv/`--python` note in installing-debuggers.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Auto-detect active venv Python and add --python override flag

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Auto-detect active virtualenv's Python interpreter via $VIRTUAL_ENV
• Add --python CLI flag to override interpreter selection
• Pass resolved Python path from CLI to daemon via DebugArgs.Python
• Wire debugpyBackend.Python field to Spawn() method
• Comprehensive unit and E2E tests for venv detection and explicit override
Diagram
flowchart LR
  CLI["CLI: dap debug script.py"]
  ResolveVenv["ResolveVenvPython()"]
  PythonFlag["--python flag"]
  DebugArgs["DebugArgs.Python"]
  Daemon["Daemon.handleDebug()"]
  Backend["debugpyBackend.Python"]
  Spawn["Spawn() uses configured Python"]
  
  CLI -->|checks $VIRTUAL_ENV| ResolveVenv
  PythonFlag -->|overrides| DebugArgs
  ResolveVenv -->|fallback| DebugArgs
  DebugArgs -->|plumbs to daemon| Daemon
  Daemon -->|sets field| Backend
  Backend -->|exec.Command| Spawn
Loading

Grey Divider

File Changes

1. backend.go ✨ Enhancement +26/-2

Add venv detection and Python field to backend

• Added ResolveVenvPython() function to detect active virtualenv's Python binary
• Modified debugpyBackend struct to include Python field
• Updated Spawn() method to use configured Python or fallback to python3

backend.go


2. backend_test.go 🧪 Tests +102/-0

Unit tests for venv resolution and backend Python

• New file with comprehensive unit tests for ResolveVenvPython()
• Tests cover: no env, missing binary, python3 detection, fallback to python, preference for python3
• Added test for debugpyBackend.Spawn() using configured Python binary

backend_test.go


3. cli.go ✨ Enhancement +7/-0

Add --python CLI flag and venv resolution logic

• Added python flag variable to newDebugCmd()
• Implemented logic to use explicit --python flag or fallback to ResolveVenvPython()
• Pass resolved Python path to DebugArgs.Python field
• Added flag definition with help text explaining venv and fallback behavior

cli.go


View more (4)
4. daemon.go ✨ Enhancement +3/-0

Wire Python path from args to backend

• Added code to set debugpyBackend.Python field from DebugArgs.Python
• Ensures daemon respects the Python interpreter path passed from CLI

daemon.go


5. e2e_test.go 🧪 Tests +78/-0

E2E tests for Python flag and venv detection

• Added runEnv() helper method to run commands with custom environment variables
• Added TestE2E_DebugPython_ExplicitPython() to verify --python flag works end-to-end
• Added TestE2E_DebugPython_BogusPythonFails() to verify invalid Python path fails cleanly
• Added TestE2E_DebugPython_VenvAutoDetect() to verify $VIRTUAL_ENV auto-detection works

e2e_test.go


6. protocol.go ✨ Enhancement +1/-0

Add Python field to DebugArgs protocol

• Added Python field to DebugArgs struct for JSON serialization
• Field is debugpy-specific and resolved from $VIRTUAL_ENV if empty

protocol.go


7. skills/debugging-code/references/installing-debuggers.md 📝 Documentation +6/-0

Document virtualenv and --python flag usage

• Added documentation section explaining virtualenv support
• Documents automatic $VIRTUAL_ENV detection behavior
• Explains --python flag override option
• Clarifies fallback to system python3 and potential issues

skills/debugging-code/references/installing-debuggers.md


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Apr 17, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Windows venv detection broken🐞 Bug ≡ Correctness
Description
ResolveVenvPython hard-codes $VIRTUAL_ENV/bin/{python3,python}, so on Windows virtualenvs (which
place the interpreter under Scripts\python.exe) will never be detected and the CLI will silently
fall back to system python3. This breaks the PR’s primary goal on Windows and makes the docs/flag
behavior misleading for that platform.
Code

backend.go[R74-85]

+func ResolveVenvPython() string {
+	venv := os.Getenv("VIRTUAL_ENV")
+	if venv == "" {
+		return ""
+	}
+	for _, name := range []string{"python3", "python"} {
+		p := filepath.Join(venv, "bin", name)
+		if _, err := os.Stat(p); err == nil {
+			return p
+		}
+	}
+	return ""
Evidence
ResolveVenvPython always joins $VIRTUAL_ENV with bin, which is not the Windows venv layout; the
repository explicitly builds/releases Windows binaries, so this is a real supported target that will
experience the bug.

backend.go[71-86]
.github/workflows/release.yml[36-59]
platform_windows.go[1-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ResolveVenvPython()` only searches `$VIRTUAL_ENV/bin/python3` and `$VIRTUAL_ENV/bin/python`. On Windows, virtualenvs typically place the interpreter under `$VIRTUAL_ENV\\Scripts\\python.exe`, so auto-detection fails and the CLI falls back to system `python3`.
### Issue Context
The repo ships Windows builds, so this is a supported platform.
### Fix Focus Areas
- backend.go[71-86]
- .github/workflows/release.yml[36-59]
- platform_windows.go[1-10]
### Suggested fix
- Add OS-specific candidate paths:
- On Windows (`runtime.GOOS == "windows"`): check `Scripts\\python.exe` (and optionally `Scripts\\python3.exe` / `python.exe`).
- On non-Windows: keep `bin/python3` then `bin/python`.
- Consider returning an absolute, cleaned path (e.g., `filepath.Clean` / `filepath.Abs`) to avoid later CWD surprises.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. debugpyBackend.Python exported unnecessarily📘 Rule violation ⚙ Maintainability
Description
The unexported struct debugpyBackend defines an exported field Python, which violates Go naming
conventions for unexported identifiers and suggests unintended API surface. This can reduce
readability and consistency across the package.
Code

backend.go[R122-125]

+// debugpyBackend spawns debugpy with Python. If Python is empty, "python3" from PATH is used.
+type debugpyBackend struct {
+	Python string
+}
Evidence
PR Compliance ID 4 requires unexported identifiers to use camelCase; debugpyBackend is unexported
but introduces an exported field Python even though it is only used internally within the package.

CLAUDE.md
backend.go[122-125]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`debugpyBackend` is unexported, but it contains an exported field `Python`. This violates the project's Go naming conventions for unexported identifiers and creates unnecessary exported surface area.
## Issue Context
The field is only used internally (within the same package) to pass an interpreter path into `Spawn`, so it does not need to be exported.
## Fix Focus Areas
- backend.go[122-134]
- daemon.go[532-534]
- backend_test.go[92-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. --python not CLI-resolved🐞 Bug ☼ Reliability
Description
When --python is provided, the CLI forwards its raw value to the daemon, and debugpyBackend.Spawn
executes it as-is; if the value is a bare name (e.g. python) or a relative path, it will be
resolved using the daemon’s PATH/CWD rather than the caller’s current environment. This can make the
override unreliable specifically for the advertised “conda / pyenv shims” use case and for relative
interpreter paths.
Code

cli.go[R219-225]

+			resolvedPython := python
+			if resolvedPython == "" {
+				resolvedPython = ResolveVenvPython()
+			}
  debugArgs := DebugArgs{
  	Breaks:           []Breakpoint(breaks),
  	StopOnEntry:      stopOnEntry,
Evidence
cli.go sets resolvedPython := python and sends it over IPC without LookPath/Abs. The daemon
simply copies args.Python into debugpyBackend.Python, and Spawn calls `exec.Command(python,
...)`, which uses PATH lookup for bare names and uses the daemon’s working directory for relative
paths. The code comment explicitly notes the daemon environment may be stale, which is why
resolution should happen in the CLI before sending the value to the daemon.

cli.go[219-232]
daemon.go[532-534]
backend.go[127-135]
daemon.go[1562-1590]
backend.go[71-74]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The CLI forwards `--python` to the daemon without resolving it. If the user passes a bare name (`python`, `py`, `conda-python`) or a relative path, the daemon will resolve it against its own PATH/CWD, which may not match the caller’s current environment.
### Issue Context
This PR explicitly aims to avoid stale daemon environment issues, so interpreter resolution should happen at CLI invocation time for both auto-detection and `--python` overrides.
### Fix Focus Areas
- cli.go[219-232]
- backend.go[127-135]
- daemon.go[532-534]
- daemon.go[1562-1590]
### Suggested fix
- In `newDebugCmd` RunE, when `python != ""`:
- If it contains a path separator (or is absolute), convert to `filepath.Abs(python)` and optionally verify existence.
- Otherwise, resolve via `exec.LookPath(python)` and pass the resulting absolute path.
- If resolution fails, return a clear CLI error and do not start/continue the debug request.
- Optionally, also `filepath.Abs` the auto-detected venv path for consistency.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread backend.go
AlmogBaku and others added 2 commits April 17, 2026 23:09
…xport field

- ResolveVenvPython now picks the correct layout per-OS: Scripts\python.exe
  on Windows, bin/python{3,} on POSIX. Returns absolute paths.
- New ResolvePythonFlag resolves --python at the CLI layer: bare names go
  through exec.LookPath (caller's PATH, not the daemon's), paths are made
  absolute. Unknown/missing binaries fail with a clear CLI error instead
  of silently falling through to the daemon.
- Rename debugpyBackend.Python -> debugpyBackend.python (unexported struct
  shouldn't expose fields).
- Unit tests for ResolvePythonFlag (abs, relative, bare-name, nonexistent,
  bare-name-not-found) and OS-aware venv tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pre-existing race: stopSession() sets d.client = nil while readLoop
dereferences d.client.ReadMessage() on the next iteration — panics with
SIGSEGV in CI under load. Pin the client to a local at loop start via
getClient(); Close() on the client will surface as a read error and the
loop exits cleanly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@AlmogBaku AlmogBaku merged commit b5bfe15 into master Apr 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant