pypa/pip · error · ValueError

Unset environment variable

Error message

Unset environment variable: {env_var_name}

What it means

Raised as ValueError in get_win_folder_from_env_vars() at line 211-212 when the CSIDL name is recognized but the corresponding environment variable (APPDATA, ALLUSERSPROFILE, or LOCALAPPDATA) is not set (None). This means the Windows shell environment is incomplete or the code is running in a non-standard Windows context where these variables are absent.

Solutions

  1. Set the missing environment variable explicitly before calling platformdirs (e.g. set APPDATA or LOCALAPPDATA).
  2. Use the ctypes backend (get_win_folder_via_ctypes) which calls SHGetKnownFolderPath and doesn't rely on env vars.
  3. Ensure the process runs in a context with a complete user profile (interactive session, not bare service).
  4. Provide fallback logic that computes the path manually (e.g. from USERPROFILE).

Example fix

// before
get_win_folder('CSIDL_APPDATA')
# ValueError: Unset environment variable: APPDATA

// after
import os
os.environ.setdefault('APPDATA', os.path.join(os.environ['USERPROFILE'], 'AppData', 'Roaming'))
get_win_folder('CSIDL_APPDATA')
Defensive patterns

Strategy: validation

Validate before calling

import os
CSIDL_TO_ENV = {'CSIDL_APPDATA': 'APPDATA', 'CSIDL_COMMON_APPDATA': 'ALLUSERSPROFILE', 'CSIDL_LOCAL_APPDATA': 'LOCALAPPDATA'}
def env_var_is_set(csidl_name: str) -> bool:
    env_var = CSIDL_TO_ENV.get(csidl_name)
    return env_var is not None and os.environ.get(env_var) is not None

Type guard

import os
def win_env_available(env_var_name: str) -> bool:
    return os.environ.get(env_var_name) is not None and bool(os.environ[env_var_name].strip())

Try / catch

import os
try:
    folder = get_win_folder('CSIDL_APPDATA')
except ValueError as e:
    if 'Unset environment variable' in str(e):
        env_var = str(e).split(': ')[-1]
        folder = os.path.join(os.environ.get('USERPROFILE', 'C:\\Users\\Default'), 'AppData', 'Roaming')
    else:
        raise

Prevention

When it happens

Trigger: Calling get_win_folder_from_env_vars for a valid CSIDL (e.g. CSIDL_APPDATA) but os.environ.get('APPDATA') returns None. This flows through get_win_folder -> _resolve_win_folder -> get_win_folder_from_env_vars on Windows systems using the env-var backend.

Common situations: Running as a Windows service or scheduled task that doesn't inherit user shell environment variables; broken Windows user profile; headless/containerized Windows where APPDATA/LOCALAPPDATA are unset; running under SYSTEM account without a user profile.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/11f264b3488355a2. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_vendor/platformdirs/windows.py:212

def get_win_folder_from_env_vars(csidl_name: str) -> str:
    """Get folder from environment variables."""
    result = get_win_folder_if_csidl_name_not_env_var(csidl_name)
    if result is not None:
        return result

    env_var_name = {
        "CSIDL_APPDATA": "APPDATA",
        "CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE",
        "CSIDL_LOCAL_APPDATA": "LOCALAPPDATA",
    }.get(csidl_name)
    if env_var_name is None:
        msg = f"Unknown CSIDL name: {csidl_name}"
        raise ValueError(msg)
    result = os.environ.get(env_var_name)
    if result is None:
        msg = f"Unset environment variable: {env_var_name}"
        raise ValueError(msg)
    return result


def get_win_folder_if_csidl_name_not_env_var(csidl_name: str) -> str | None:  # ruff:ignore[too-many-return-statements]
    """Get a folder for a CSIDL name that does not exist as an environment variable."""
    if csidl_name == "CSIDL_PERSONAL":
        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents")  # ruff:ignore[os-path-join]

    if csidl_name == "CSIDL_DOWNLOADS":
        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Downloads")  # ruff:ignore[os-path-join]

    if csidl_name == "CSIDL_MYPICTURES":
        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures")  # ruff:ignore[os-path-join]

    if csidl_name == "CSIDL_MYVIDEO":
        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Videos")  # ruff:ignore[os-path-join]

    if csidl_name == "CSIDL_MYMUSIC":

View on GitHub (pinned to f399c37189)