Skip to content

Pyright LSP: Virtual environment packages not resolved due to incorrect workspace/configuration handling #6131

@minzique

Description

@minzique

Bug Description

Pyright LSP fails to resolve packages from virtual environments in monorepo setups, even when a .venv symlink exists at the project root. The LSP reports Import "X" could not be resolved for all third-party packages.

Environment

  • OpenCode version: Latest (Dec 25, 2025)
  • OS: macOS (Darwin)
  • Python: 3.12
  • Pyright version: 1.1.407

Reproduction Steps

  1. Have a monorepo with a Python project in a subdirectory (e.g., apps/api/)
  2. Virtual environment is at apps/api/.venv
  3. Create a symlink at project root: ln -s apps/api/.venv .venv
  4. Have a pyrightconfig.json at project root
  5. Run OpenCode and open a Python file
  6. LSP diagnostics show import errors for installed packages

Expected Behavior

Pyright LSP should resolve packages from the virtual environment, similar to how the CLI works:

# CLI pyright works correctly:
$ .venv/bin/pyright apps/api/app/main.py
# Only shows 3 real errors, no import errors

# But LSP in OpenCode shows:
# error[Pyright] (reportMissingImports): Import "fastapi" could not be resolved
# error[Pyright] (reportMissingImports): Import "uvicorn" could not be resolved

Root Cause Analysis

After analyzing the source code, I identified the issue in packages/opencode/src/lsp/client.ts:

Current Implementation (lines 67-70):

connection.onRequest("workspace/configuration", async () => {
  // Return server initialization options
  return [input.server.initialization ?? {}]
})

The Problem

  1. Ignores request parameters: The handler ignores the ConfigurationParams which contains items specifying which configuration sections are being requested.

  2. Pyright requests multiple sections: Pyright makes separate configuration requests for:

    • python section (for pythonPath, venvPath)
    • python.analysis section (for stubPath, typeshedPaths, etc.)
  3. Same response for all requests: OpenCode returns the same flat object regardless of which section is requested, which breaks pyright's expectation of getting section-specific configuration.

How Pyright Uses Configuration (from pyright's server.ts):

// Pyright requests the 'python' section
const pythonSection = await this.getConfiguration(workspace.rootUri, 'python');
if (pythonSection) {
    const pythonPath = pythonSection.pythonPath;
    // ...
    const venvPath = pythonSection.venvPath;
    // ...
}

// Pyright requests the 'python.analysis' section separately
const pythonAnalysisSection = await this.getConfiguration(workspace.rootUri, 'python.analysis');
// ...

OpenCode's Pyright Initialization (server.ts lines 523-537):

const initialization: Record<string, string> = {}

const potentialVenvPaths = [process.env["VIRTUAL_ENV"], path.join(root, ".venv"), path.join(root, "venv")]
for (const venvPath of potentialVenvPaths) {
  const potentialPythonPath = path.join(venvPath, "bin", "python")
  if (await Bun.file(potentialPythonPath).exists()) {
    initialization["pythonPath"] = potentialPythonPath  // Sets flat key
    break
  }
}

This sets pythonPath correctly, but pyright also needs venvPath to be set for proper package resolution without running the interpreter.

Suggested Fix

Option 1: Properly handle configuration sections

connection.onRequest("workspace/configuration", async (params: ConfigurationParams) => {
  const results: any[] = [];
  
  for (const item of params.items) {
    if (item.section === 'python') {
      results.push({
        pythonPath: input.server.initialization?.pythonPath,
        venvPath: input.server.initialization?.venvPath,
      });
    } else if (item.section === 'python.analysis') {
      results.push(input.server.initialization?.pythonAnalysis ?? {});
    } else {
      results.push(input.server.initialization ?? {});
    }
  }
  
  return results;
})

Option 2: Add venvPath to initialization

In server.ts Pyright spawn function:

const potentialVenvPaths = [process.env["VIRTUAL_ENV"], path.join(root, ".venv"), path.join(root, "venv")]
for (const venvPath of potentialVenvPaths) {
  const potentialPythonPath = path.join(venvPath, "bin", "python")
  if (await Bun.file(potentialPythonPath).exists()) {
    initialization["pythonPath"] = potentialPythonPath
    initialization["venvPath"] = path.dirname(venvPath)  // Add parent directory
    initialization["venv"] = path.basename(venvPath)      // Add venv folder name
    break
  }
}

Additional Context

  • The CLI pyright command works correctly because it reads pyrightconfig.json
  • The pyright-langserver should also read pyrightconfig.json, but the LSP settings seem to override or interfere with it
  • Setting pythonPath alone requires pyright to execute the Python interpreter to discover sys.path, which may fail in certain environments
  • Setting venvPath + venv directly tells pyright where to find site-packages without needing to run Python

Workaround

Currently, no reliable workaround exists within OpenCode. Users must rely on pyrightconfig.json being read correctly, which doesn't appear to work with the LSP integration.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions