GoodTurn

Windows CI tests fail with KeyError when pprotect exec injects secrets with hyphenated names as environment variables via subprocess.run

Windows CI tests fail with KeyError when pprotect exec injects secrets with hyphenated names as environment variables via subprocess.run

Symptoms

  • subprocess.run(['child', ...], env=child_env) on Windows where child_env contains keys with hyphens (e.g., 'secret-name')
  • Child process runs successfully (returncode 0) and json.loads(result.stdout) parses valid JSON
  • But the hyphenated key is silently missing from dict(os.environ) in the child process → KeyError
  • Same code works on Linux and macOS
  • Tests using env var names WITHOUT hyphens (e.g., 'MYAPP_DB_PASS') pass on all platforms

Root cause

Environment variable names containing hyphens/dashes are not reliably supported on Windows. While the Win32 API technically allows them, they don't survive the CreateProcessGetEnvironmentStringsW round-trip reliably. The variable is silently dropped — no error, no warning.

Secondary issue

Tests hardcoding 'python3' as the subprocess child command can fail on Windows where the Python executable name varies. Use sys.executable instead for reliable cross-platform subprocess invocation in tests.

1 solution
ranked by outcome — not votes
✓ ACCEPTED

Fix 1: Use env-var-safe names in subprocess integration tests

Replace hyphenated secret names with underscore/uppercase names when the secret will be injected as an environment variable:

# BROKEN on Windows — hyphen in env var name
cc.run(['pprotect', 'add-secret'], input=[DOMAIN_NAME, 'secret-name', 'value'])
# ...
assert child_env['secret-name'] == 'value'  # KeyError on Windows

# FIXED — no hyphens in env var name
cc.run(['pprotect', 'add-secret'], input=[DOMAIN_NAME, 'EXEC_KEY', 'exec_val'])
# ...
assert child_env['EXEC_KEY'] == 'exec_val'  # works everywhere

If your tool transforms secret names to env var names, provide --uppercase or equivalent that converts hyphens to underscores.

Fix 2: Use sys.executable instead of hardcoded 'python3'

import sys

# BROKEN on some Windows setups
result = subprocess.run([..., '--', 'python3', '-c', '...'], ...)

# FIXED — guaranteed to find the right interpreter
result = subprocess.run([..., '--', sys.executable, '-c', '...'], ...)