pub async fn find_venv( file_path: &Path, git_toplevel: Option<&Path>,) -> Result<Option<PathBuf>, VenvError> { // Start from file's parent directory let mut current = file_path.parent(); let mut depth = 0; while let Some(dir) = current { // Stop if we exceed git toplevel if let Some(toplevel) = git_toplevel { if !dir.starts_with(toplevel) { break; } } // Check for .venv/pyvenv.cfg existence let venv_path = dir.join(VENV_DIR); let pyvenv_cfg = venv_path.join(PYVENV_CFG); if pyvenv_cfg.exists() { return Ok(Some(venv_path)); } // Move to parent directory current = dir.parent(); depth += 1; } Ok(None) // No .venv found}
If git rev-parse --show-toplevel fails (not in a git repo), search continues up to filesystem root (/ on Unix, drive root on Windows).Code reference:src/venv.rs:9-32
Copy
pub async fn get_git_toplevel(working_dir: &Path) -> Result<Option<PathBuf>, VenvError> { let output = match Command::new("git") .args(["rev-parse", "--show-toplevel"]) .current_dir(working_dir) .output() .await { Ok(output) => output, Err(e) => { tracing::warn!(error = ?e, "git command failed, continuing without git"); return Ok(None); // Not an error — just no git boundary } }; if output.status.success() { let path_str = String::from_utf8_lossy(&output.stdout); let path = PathBuf::from(path_str.trim()); Ok(Some(path)) } else { Ok(None) // Not in a git repository }}
Without a git boundary, venv search may traverse very deep directory structures (e.g., from /home/user/workspace/project/subproject/src/file.py up to /home/user/.venv). Always run typemux-cc inside a git repository for best performance.
cd /home/user/monorepols .venv/pyvenv.cfg# → exists# typemux-cc starts# → Pre-spawn backend with VIRTUAL_ENV=/home/user/monorepo/.venv
Logs:
Copy
[INFO] Fallback .venv found at git toplevel venv=/home/user/monorepo/.venv[INFO] Pre-spawning fallback backend
Copy
cd /home/user/monorepo/project-als .venv/pyvenv.cfg# → existsls /home/user/monorepo/.venv/pyvenv.cfg# → does not exist# typemux-cc starts# → Pre-spawn backend with VIRTUAL_ENV=/home/user/monorepo/project-a/.venv
Logs:
Copy
[INFO] Fallback .venv found at cwd venv=/home/user/monorepo/project-a/.venv[INFO] Pre-spawning fallback backend
Copy
cd /home/user/monorepols .venv/pyvenv.cfg# → does not exist# typemux-cc starts# → No pre-spawned backend (backends created on first didOpen)
Logs:
Copy
[WARN] No fallback .venv found[INFO] Starting without pre-spawned backend
# No .venv exists yetcd /home/user/project# Claude Code opens main.py# typemux-cc searches for .venv → not found# Error returned: "lsp-proxy: .venv not found (strict mode)"
Cache state:
Copy
OpenDocument { uri: "file:///home/user/project/main.py", venv: None, // ← Cached as "no venv"}
2
User creates .venv
Copy
# User runs uv init or creates .venv manuallyuv venv# .venv now exists!ls .venv/pyvenv.cfg# → exists
3
Cached venv still None
Copy
# User triggers textDocument/hover in Claude Code# typemux-cc looks up cache → venv: None (stale!)# Still returns error (no re-search performed)
Why?textDocument/hover on a cached file does not trigger venv re-search.
4
Workaround: Reopen file
Copy
# User closes and reopens main.py in Claude Code# → textDocument/didClose (clears cache)# → textDocument/didOpen (re-searches venv)# → .venv found! Backend spawned
# Show all venv search attemptsgrep "Starting .venv search" /tmp/typemux-cc.log# Show successful detectionsgrep ".venv found" /tmp/typemux-cc.log# Show failuresgrep "No .venv found" /tmp/typemux-cc.log
Contains metadata: pyvenv.cfg includes home path to base Python interpreter
If your virtual environment doesn’t have pyvenv.cfg, it’s likely not a standard virtualenv. typemux-cc only supports standard .venv environments (see Architecture: Non-Goals).