Python type-checker LSP multiplexer for Claude Code — pyright, ty, pyrefly
Quickstart ◆ Problems Solved ◆ Backends ◆ Installation ◆ Typical Use Case ◆ Architecture
Claude Code's official pyright plugin spawns a single LSP backend at startup and holds onto it. If .venv doesn't exist yet — or you create a new one later — it never picks it up. You have to restart Claude Code.
This is especially painful with git worktrees, now common in AI-assisted development: you spin up a fresh worktree, create .venv, and then must restart Claude Code just to get type-checking.
typemux-cc is a Python LSP proxy that fixes this — .venv changes are reflected within your running session, no restarts required.
# 1. Install a backend (pyright recommended)
npm install -g pyright
# 2. Disable the official pyright plugin
/plugin disable pyright-lsp@claude-plugins-official
# 3. Add marketplace and install
/plugin marketplace add K-dash/typemux-cc
/plugin install typemux-cc@typemux-cc-marketplace
# 4. Restart Claude Code (initial installation only)For ty/pyrefly, set
TYPEMUX_CC_BACKENDin your config.
- ⚡ Late
.venvcreation (worktrees, hooks) — Spin up a git worktree, create.venvlater, and typemux-cc picks it up on the next file open. No Claude Code restart needed. - 🔄 Multi-project venv switching (monorepos) — typemux-cc keeps a per-
.venvbackend pool and routes requests to the correct one. Switching between projects is instant. - 🔀 Multi-backend support — Not locked into pyright. Choose between pyright, ty, or pyrefly — switch via a single env var.
Why LSP over text search? In monorepos, grep returns false positives from same-named types across projects. LSP resolves references at the type-system level. See real-world benchmarks.
| Backend | Command | Status |
|---|---|---|
| pyright | pyright-langserver --stdio |
✅ Stable (default if TYPEMUX_CC_BACKEND is not set) |
| ty | ty server |
🧪 Experimental (verified) |
| pyrefly | pyrefly lsp |
🧪 Experimental (verified) |
| Platform | Architecture |
|---|---|
| macOS | arm64 only |
| Linux | x86_64 / arm64 |
Note
Windows is currently unsupported (due to path handling differences). Intel macOS users must build from source (prebuilt binaries are arm64 only).
- One of the supported LSP backends available in PATH:
pyright-langserver(install vianpm install -g pyrightorpip install pyright)ty(install viapip install tyoruvx ty)pyrefly(install viapip install pyrefly)
- Git (used to determine
.venvsearch boundary, works without it)
Note
Claude Code restart is required only for initial installation. After installation, .venv creation and switching no longer require restarts.
# pyright (default, recommended)
npm install -g pyright
# ty (experimental — by the creators of uv)
pip install ty
# pyrefly (experimental — by Meta)
pip install pyreflyImportant
You must disable the official pyright plugin. Having both enabled causes conflicts.
/plugin disable pyright-lsp@claude-plugins-officialNote
Installation uses GitHub API and curl. It may fail in offline environments or under rate limiting.
# 1. Add marketplace
/plugin marketplace add K-dash/typemux-cc
# 2. Install plugin
/plugin install typemux-cc@typemux-cc-marketplace
# 3. Restart Claude Code (initial installation only)After installation, verify in ~/.claude/settings.json:
{
"enabledPlugins": {
"pyright-lsp@claude-plugins-official": false,
"typemux-cc@typemux-cc-marketplace": true
}
}# Update
/plugin update typemux-cc@typemux-cc-marketplace
# Uninstall
/plugin uninstall typemux-cc@typemux-cc-marketplace
/plugin marketplace remove typemux-cc-marketplaceRequires Rust 1.75 or later.
git clone https://github.com/K-dash/typemux-cc.git
cd typemux-cc
cargo build --release
/plugin marketplace add /path/to/typemux-cc
/plugin install typemux-cc@typemux-cc-marketplace
# Restart Claude Code (initial installation only)Automatically starts as a Claude Code plugin. For manual execution:
./target/release/typemux-cc
./target/release/typemux-cc --help# Via CLI flag
./target/release/typemux-cc --backend ty
# Via environment variable
TYPEMUX_CC_BACKEND=ty ./target/release/typemux-ccTo configure the backend via the wrapper script (persistent across sessions):
mkdir -p ~/.config/typemux-cc
cat > ~/.config/typemux-cc/config << 'EOF'
# Select backend (pyright, ty, or pyrefly)
export TYPEMUX_CC_BACKEND="pyright"
# Enable file output
export TYPEMUX_CC_LOG_FILE="/tmp/typemux-cc.log"
EOFDefault output is stderr. For file output:
TYPEMUX_CC_LOG_FILE=/tmp/typemux-cc.log ./target/release/typemux-cc| Environment Variable | Description | Default |
|---|---|---|
TYPEMUX_CC_LOG_FILE |
Log file path | Not set (stderr only) |
TYPEMUX_CC_BACKEND |
LSP backend to use | pyright |
TYPEMUX_CC_MAX_BACKENDS |
Max concurrent backend processes | 8 |
TYPEMUX_CC_BACKEND_TTL |
Backend TTL in seconds (0 = disabled) | 1800 |
RUST_LOG |
Log level | typemux_cc=debug |
For config file method and details, see ARCHITECTURE.md.
A common workflow with AI coding agents:
my-project/ # main worktree
├── .venv/
└── src/main.py
my-project-worktree/ # new worktree (no .venv yet)
└── src/main.py
| Step | What Happens |
|---|---|
| 1. Create worktree | git worktree add ../my-project-worktree feat/new-feature — no .venv exists |
2. Create .venv |
cd ../my-project-worktree && uv sync — .venv now exists |
| 3. Open a file | Claude Code opens my-project-worktree/src/main.py → typemux-cc detects the new .venv and spawns a backend automatically |
With the official plugin, step 3 would require restarting Claude Code. With typemux-cc, it just works.
my-monorepo/
├── project-a/
│ ├── .venv/ # project-a specific virtual environment
│ └── src/main.py
├── project-b/
│ ├── .venv/ # project-b specific virtual environment
│ └── src/main.py
└── project-c/
├── .venv/ # project-c specific virtual environment
└── src/main.py
| Claude Code Action | Proxy Behavior |
|---|---|
| 1. Session starts | Search for fallback .venv (start without venv if not found) |
2. Opens project-a/src/main.py |
Detect project-a/.venv → spawn backend (session 1), add to pool |
3. Opens project-b/src/main.py |
Detect project-b/.venv → spawn backend (session 2), add to pool |
4. Returns to project-a/src/main.py |
project-a/.venv already in pool → route to session 1 (no restart) |
When Claude Code moves from project-a/main.py to project-b/main.py:
- Proxy detects different
.venv(project-a/.venv → project-b/.venv) - Checks the backend pool —
project-b/.venvnot found - Spawns new backend with
VIRTUAL_ENV=project-b/.venv(session 2) - Session 1 (project-a) stays alive in the pool — no restart
- Restores open documents under project-b/ to session 2
- Clears diagnostics for documents outside project-b/
- All LSP requests for project-b files now use project-b dependencies
When Claude Code returns to project-a/main.py later, session 1 is still in the pool — zero restart overhead.
Backends are evicted only when the pool is full (LRU) or after idle timeout (TTL, default 30 min).
From the user's perspective: Nothing visible happens. LSP just works.
Each backend process is spawned with VIRTUAL_ENV and PATH set to point at the detected .venv. These are only applied to the child backend process — your shell environment and system PATH are never modified.
Tip: Enable file logging first: add
TYPEMUX_CC_LOG_FILE=/tmp/typemux-cc.logto your config.
which pyright-langserver # Check if backend is in PATH (or: which ty, which pyrefly)
cat ~/.claude/settings.json | grep typemux # Check plugin settings
tail -100 /tmp/typemux-cc.log # Check logsDue to a known Claude Code issue, /plugin update may not refresh the cached plugin files. If you still see the old version after updating, manually clear the cache:
# 1. Remove cached plugin
rm -rf ~/.claude/plugins/cache/typemux-cc-marketplace/
# 2. Reinstall
/plugin install typemux-cc@typemux-cc-marketplace
# 3. Restart Claude Code- Verify
.venv/pyvenv.cfgexists - Verify file is within git repository
- Check the log for
venv_path=None— this means the document was cached before.venvexisted - If
.venvwas created after the file was opened, reopen the file to trigger venv re-detection - Use
RUST_LOG=tracefor detailed venv search logs
Note
Why does this happen? typemux-cc caches the venv for each document on first open. If .venv doesn't exist yet (e.g., created later by a hook), the cache stores None. Subsequent requests reuse the cached value without re-searching. Reopening the file clears the cache entry and triggers a fresh search.
| Item | Limitation | Workaround |
|---|---|---|
| Windows unsupported | Path handling assumes Unix-like systems | Use WSL2 |
| macOS Intel unsupported | Prebuilt is arm64 only | Use Apple Silicon |
| Fixed venv name | Only .venv with pyvenv.cfg — intentionally strict to avoid silently wrong environments (poetry/conda/etc. not supported) |
Rename to .venv or create a .venv symlink |
| Symlinks | May fail to detect pyvenv.cfg if .venv is a symlink |
Use actual directory |
Late .venv creation |
venv cached as None if .venv didn't exist when file was opened |
Reopen the file after creating .venv |
| setuptools editable installs | Not a typemux-cc bug. All LSP backends (pyright, ty, pyrefly) cannot resolve imports from setuptools-style editable installs that use import hooks (ty#475) | Switch build backend to hatchling/flit, or add source paths to extra-paths in backend config |
For design philosophy, state transitions, and internal implementation details, see:
This project is licensed under the MIT License - see the LICENSE file for details.