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
- Set the missing environment variable explicitly before calling platformdirs (e.g. set APPDATA or LOCALAPPDATA).
- Use the ctypes backend (get_win_folder_via_ctypes) which calls SHGetKnownFolderPath and doesn't rely on env vars.
- Ensure the process runs in a context with a complete user profile (interactive session, not bare service).
- 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
- Ensure APPDATA, LOCALAPPDATA, and ALLUSERSPROFILE are set in service/scheduled-task environments.
- Use get_win_folder_via_ctypes (SHGetKnownFolderPath) as fallback when env vars are absent.
- Set fallback environment variables in headless/containerized Windows contexts.
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
- should only be used on Unix
- Unknown CSIDL name
- non-local file URIs are not supported on this platform
- SHGetKnownFolderPath returned NULL for
- Can not use any platform or abi specific options unless…
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)