Skip to main content

Search Algorithm

typemux-cc searches for .venv by traversing parent directories upward from the opened file, stopping at the git repository root.

Search Flow

Implementation Details

Code reference: src/venv.rs:34-96
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
}

Search Rules

1

Starting point

Parent directory of opened file (not the file itself)
2

Verification

Check existence of .venv/pyvenv.cfg (not just .venv directory)
3

Boundary

Git repository root (obtained at startup via git rev-parse --show-toplevel)
4

Direction

Traverse upward through parent directories until boundary or .venv found

Example Traversal

monorepo/                     # git toplevel
├── .venv/                    # ✅ Found at depth 2
│   └── pyvenv.cfg
├── project-a/
│   └── src/
│       └── main.py           # ← File opened here
└── project-b/
    ├── .venv/                # ❌ Not searched (different branch)
    │   └── pyvenv.cfg
    └── app.py
Search path for project-a/src/main.py:
  1. Check project-a/src/.venv/pyvenv.cfg → ❌
  2. Check project-a/.venv/pyvenv.cfg → ❌
  3. Check monorepo/.venv/pyvenv.cfg → ✅ Found

Git Toplevel Boundary

Why Git Toplevel?

The git repository root provides a natural boundary for venv search:
  • Prevents escaping workspace: Stops search from going into parent directories outside the project
  • Performance: Limits search depth in deeply nested directory structures
  • Semantic boundary: Git repo typically == project boundary

Fallback When Not in Git Repo

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
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.

Fallback .venv Search Order

At startup (before any files are opened), typemux-cc attempts to pre-spawn a backend with a fallback venv.

Search Order

1

1. Git toplevel

Check .venv at $(git rev-parse --show-toplevel)/.venv
git rev-parse --show-toplevel
# → /home/user/monorepo

# Check: /home/user/monorepo/.venv/pyvenv.cfg
2

2. Current working directory

Check .venv at $PWD/.venv
pwd
# → /home/user/monorepo/project-a

# Check: /home/user/monorepo/project-a/.venv/pyvenv.cfg
3

3. No fallback

Start without pre-spawned backend (backends created on-demand)
Code reference: src/venv.rs:98-155

Fallback Behavior Examples

cd /home/user/monorepo
ls .venv/pyvenv.cfg
# → exists

# typemux-cc starts
# → Pre-spawn backend with VIRTUAL_ENV=/home/user/monorepo/.venv
Logs:
[INFO] Fallback .venv found at git toplevel venv=/home/user/monorepo/.venv
[INFO] Pre-spawning fallback backend

Cache Behavior and Limitations

Document Cache

When a file is opened via textDocument/didOpen, typemux-cc caches:
  • URI: file:///path/to/file.py
  • Language ID: python
  • Version: LSP document version number
  • Text: Full file contents
  • Venv: Resolved .venv path (or None if not found)
Code reference: src/state.rs:30-37
pub struct OpenDocument {
    pub language_id: String,
    pub version: i32,
    pub text: String,
    pub venv: Option<PathBuf>,  // ← Cached venv path
}

When Venv is Re-searched

Always Re-search

  • textDocument/didOpen (explicit file open)
  • Cache miss on URI-bearing request (file not in cache)

Never Re-search

  • textDocument/didChange (uses cached venv)
  • textDocument/hover on cached file (uses cached venv)
  • Any request for a file already in cache

Cache Limitations

Critical limitation: Creating .venv after opening a file will not take effect until the file is reopened.

Example Problem Scenario

1

User opens file without .venv

# No .venv exists yet
cd /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:
OpenDocument {
    uri: "file:///home/user/project/main.py",
    venv: None,  // ← Cached as "no venv"
}
2

User creates .venv

# User runs uv init or creates .venv manually
uv venv

# .venv now exists!
ls .venv/pyvenv.cfg
# → exists
3

Cached venv still None

# 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

# User closes and reopens main.py in Claude Code
# → textDocument/didClose (clears cache)
# → textDocument/didOpen (re-searches venv)
# → .venv found! Backend spawned
New cache state:
OpenDocument {
    uri: "file:///home/user/project/main.py",
    venv: Some("/home/user/project/.venv"),  // ← Updated
}

Workarounds for Stale Cache

Cleanest solution — clears all cached state.
# macOS
Cmd+Q (quit Claude Code)
# Reopen Claude Code

# Linux
killall claude-code  # or use your window manager
# Reopen Claude Code
Pros: Guaranteed to work
Cons: Loses all open tabs, window state, etc.
Lightweight — only clears cache for specific file.In Claude Code:
  1. Close the file tab
  2. Reopen the file from file explorer
Triggers:
  • textDocument/didClose → cache entry removed
  • textDocument/didOpen → fresh venv search
Pros: No need to restart editor
Cons: Must repeat for each open file
Best practice — avoids the problem entirely.
# Always create .venv first
cd /home/user/project
uv venv  # or python -m venv .venv

# Then open Claude Code
code .
Pros: No cache staleness
Cons: Requires discipline

Debugging Venv Detection

Enable Detailed Logging

# Trace-level logs show each search step
RUST_LOG=trace TYPEMUX_CC_LOG_FILE=/tmp/typemux-cc.log typemux-cc

Useful Log Queries

# Show all venv search attempts
grep "Starting .venv search" /tmp/typemux-cc.log

# Show successful detections
grep ".venv found" /tmp/typemux-cc.log

# Show failures
grep "No .venv found" /tmp/typemux-cc.log

Manual Verification

To manually check if typemux-cc will find your .venv:
cd /path/to/your/project

# Check git toplevel
git rev-parse --show-toplevel
# → Should output project root

# Check .venv exists with pyvenv.cfg
ls .venv/pyvenv.cfg
# → Should exist

# Start typemux-cc
RUST_LOG=debug TYPEMUX_CC_LOG_FILE=/tmp/typemux-cc.log typemux-cc

# Check logs
grep "Fallback .venv found" /tmp/typemux-cc.log

Why pyvenv.cfg?

typemux-cc checks for .venv/pyvenv.cfg (not just .venv directory) because:
  1. Standard marker: All virtualenvs created by python -m venv, virtualenv, or uv venv contain pyvenv.cfg
  2. Avoids false positives: Prevents detecting unrelated .venv directories (e.g., manually created folders)
  3. 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).

Performance Characteristics

  • Search time: O(depth) — typically 1-5 directory checks
  • Cache lookup: O(1) — hash map lookup by URI
  • Git toplevel: Cached on first call (single git subprocess at startup)
Venv search is not a performance bottleneck. The search happens only on file open (once per file) and is fast (< 1ms for typical depths).