DEV Community

Cover image for uv on Windows with OneDrive: Centralized Virtual Environments
williamandresr
williamandresr

Posted on

uv on Windows with OneDrive: Centralized Virtual Environments

If you use uv on Windows and your projects live in OneDrive, you've probably already run into the problem: OneDrive tries to sync the .venv folder and everything breaks — slow sync, locked files, corrupted environments.

The solution is to centralize virtual environments outside of OneDrive and have uv use them automatically.


The Problem

uv creates the virtual environment inside each project:

OneDrive/
└── dev/
    └── my-project/
        ├── .venv/          ← OneDrive tries to sync this
        └── pyproject.toml
Enter fullscreen mode Exit fullscreen mode

A typical .venv folder contains thousands of small files. OneDrive doesn't know to ignore them and syncs everything, causing slowdowns, conflicts, and sometimes corrupted environments.


The Solution

Centralize environments outside of OneDrive using the UV_PROJECT_ENVIRONMENT environment variable:

C:\Users\username\
├── OneDrive\dev\my-project\   ← code, synced by OneDrive
└── .venvs\my-project\         ← environment, outside OneDrive
Enter fullscreen mode Exit fullscreen mode

uv respects UV_PROJECT_ENVIRONMENT and creates the environment wherever you point it.


Setup

1. PowerShell Profile ($PROFILE)

The profile automatically detects whether you're in a uv project and sets the corresponding environment variables:

# ── Workflow ──────────────────────────────────────────────────────
# 1. cd my-project      → activates UV_PROJECT_ENVIRONMENT and VIRTUAL_ENV
# 2. uv init            → initializes the project
# 3. uv venv            → creates environment in ~/.venvs/<project>
# 4. uv add <package>   → installs dependencies
# 5. clean-venvs        → removes orphaned environments
#
# Notes:
# - Use "cd .." with a space, not "cd.." to clear variables
# - UV_PROJECT_ENVIRONMENT is only set if pyproject.toml exists
# ─────────────────────────────────────────────────────────────────

# ── uv: centralized venvs ────────────────────────────────────────
function Set-UvEnv {
    $venvName = Split-Path $PWD -Leaf
    $isProject = Test-Path "$PWD\pyproject.toml" -PathType Leaf
    if ($venvName -and $isProject) {
        $env:UV_PROJECT_ENVIRONMENT = "$env:USERPROFILE\.venvs\$venvName"
        $venvPath = "$env:USERPROFILE\.venvs\$venvName"
        if (Test-Path $venvPath) {
            $env:VIRTUAL_ENV = $venvPath
        } else {
            $env:VIRTUAL_ENV = $null
        }
    } else {
        $env:UV_PROJECT_ENVIRONMENT = $null
        $env:VIRTUAL_ENV = $null
    }
}

function Set-LocationWithEnv {
    param($Path)
    if (-not $Path) { $Path = $env:USERPROFILE }
    Set-Location $Path
    Set-UvEnv
}
Set-Alias -Name cd -Value Set-LocationWithEnv -Force -Option AllScope

function cddot { Set-LocationWithEnv .. }
Set-Alias -Name 'cd..' -Value cddot -Force -Option AllScope

# Initialize on terminal startup
Set-UvEnv

# uv venv points to centralized directory
function uv {
    if ($args[0] -eq 'venv' -and $args.Count -eq 1) {
        if (-not (Test-Path "$PWD\pyproject.toml")) {
            Write-Host "Run uv init first" -ForegroundColor Yellow
            return
        }
        & (Get-Command uv -CommandType Application) venv $env:UV_PROJECT_ENVIRONMENT
    } elseif ($args[0] -eq 'init') {
        & (Get-Command uv -CommandType Application) @args
        Set-UvEnv
    } else {
        & (Get-Command uv -CommandType Application) @args
    }
}

# Prompt with venv indicator
function prompt {
    $venvPath = "$env:USERPROFILE\.venvs\$(Split-Path $PWD -Leaf)"
    if (Test-Path $venvPath) {
        Write-Host "[ven√] " -NoNewline -ForegroundColor Blue
    }
    "PS $($PWD)> "
}
# ─────────────────────────────────────────────────────────────────

# ── orphaned environment cleanup ─────────────────────────────────
function Remove-OrphanEnvs {
    $venvs = Get-ChildItem "$env:USERPROFILE\.venvs" -Directory
    foreach ($venv in $venvs) {
        $projectPath = "$env:USERPROFILE\OneDrive\dev\$($venv.Name)"
        if (-not (Test-Path "$projectPath\pyproject.toml")) {
            Write-Host "Orphan: $($venv.Name)" -ForegroundColor Yellow
            $confirm = Read-Host "Delete? (y/n)"
            if ($confirm -eq "y") {
                Remove-Item -Recurse -Force $venv.FullName
                Write-Host "Deleted" -ForegroundColor Red
            }
        }
    }
}
Set-Alias -Name clean-venvs -Value Remove-OrphanEnvs
# ─────────────────────────────────────────────────────────────────
Enter fullscreen mode Exit fullscreen mode

Adjust the path $env:USERPROFILE\OneDrive\dev in Remove-OrphanEnvs to wherever your projects live.

2. VS Code (settings.json)

"python.venvPath": "~/.venvs",
"python-envs.terminal.autoActivationType": "off",
"python.useEnvironmentsExtension": true,
Enter fullscreen mode Exit fullscreen mode

python.venvPath tells VS Code where to look for environments for IntelliSense and the interpreter selector. autoActivationType: off disables VS Code's automatic activation, since the PowerShell profile handles that.


How It Works

Navigating into a project

cd my-project
Enter fullscreen mode Exit fullscreen mode

The cd alias calls Set-UvEnv, which checks whether pyproject.toml exists in the folder. If it does, it sets UV_PROJECT_ENVIRONMENT and VIRTUAL_ENV pointing to ~/.venvs/my-project. The prompt shows [venv√] if the environment has already been created.

Leaving a project

cd ..
Enter fullscreen mode Exit fullscreen mode

Set-UvEnv detects that the new folder has no pyproject.toml and clears both variables. uv stops pointing to the previous environment.

Full workflow for a new project

cd my-project       # activates UV_PROJECT_ENVIRONMENT
uv init             # creates pyproject.toml and updates variables automatically
uv venv             # creates the environment in ~/.venvs/my-project
uv add pandas       # installs dependencies into the centralized environment
Enter fullscreen mode Exit fullscreen mode

If you try uv venv before uv init, the profile shows a warning:

Run uv init first
Enter fullscreen mode Exit fullscreen mode

Cleaning up orphaned environments

When you delete a project, its environment in ~/.venvs becomes orphaned. To clean it up:

clean-venvs
Enter fullscreen mode Exit fullscreen mode

The script iterates over ~/.venvs, finds environments with no corresponding project, and asks before deleting each one.


Design Decisions

Why validate pyproject.toml instead of just the folder name?

Without that check, UV_PROJECT_ENVIRONMENT would be set for any folder, including system folders. If the terminal opens in C:\Windows\System32, for example, it would create ~/.venvs/System32.

Why is VIRTUAL_ENV only set if the environment already exists?

VS Code reads VIRTUAL_ENV to detect the interpreter. If it points to a non-existent path, VS Code may show errors or fail to detect the environment correctly when creating a new project.

Why the cd.. alias in addition to cd?

PowerShell allows writing cd.. without a space, but in that case it bypasses aliases and runs the native command, without calling Set-UvEnv. The explicit cd.. alias ensures variables are also cleared in that case.


Limitations

This solution assumes the project folder name matches the environment name in ~/.venvs. If you have projects with duplicate names in different subfolders of OneDrive, there may be collisions.

It also requires the profile to be installed on every machine you work on. It's portable — no external dependencies — but it is a manual step when setting up a new machine.

Top comments (0)