# GitHub Issue #1125 Investigation Report

## Issue Summary

**Title:** Sudo fails to ask for a password if locale of remote system is not English

**Reporter:** philippemilink
**Status:** Open
**Labels:** bug

**Problem Description:**
When a remote system is configured with a non-English locale (e.g., `fr_FR.UTF-8`, `de_DE.UTF-8`), pyinfra fails to detect that sudo requires a password. This causes commands with `_sudo=True` to fail instead of prompting the user for their sudo password.

**Example Error (French locale):**
```
sudo: il est necessaire de saisir un mot de passe
```

The expected behavior is that pyinfra should detect this message and prompt the user for their sudo password, regardless of the system locale.

---

## Root Cause Analysis

### Primary Issue Location

**File:** `/Users/nick/Dev/Webroot/pyinfra/src/src/pyinfra/connectors/util.py`
**Function:** `execute_command_with_sudo_retry()` (lines 197-217)

### The Problem

The function `execute_command_with_sudo_retry()` uses hardcoded English string matching to detect when sudo requires a password:

```python
def execute_command_with_sudo_retry(
    host: "Host",
    command_arguments: "ConnectorArguments",
    execute_command: Callable[..., tuple[int, CommandOutput]],
) -> tuple[int, CommandOutput]:
    return_code, output = execute_command()

    if return_code != 0 and output and output.combined_lines:
        for line in reversed(output.combined_lines):
            if line.line.strip() == "sudo: a password is required":  # <-- PROBLEM
                # ... prompt for password and retry
```

**Line 209** checks for the exact English string `"sudo: a password is required"`, which only works when the remote system's locale is set to English. On non-English systems, sudo outputs localized messages:

| Locale | Sudo Message |
|--------|--------------|
| `en_US` | `sudo: a password is required` |
| `fr_FR` | `sudo: il est necessaire de saisir un mot de passe` |
| `de_DE` | `sudo: Ein Passwort ist erforderlich` |

### Why This Happens

1. When `_sudo=True` is used without a password, pyinfra runs `sudo -H -n ...` (the `-n` flag disables interactive prompts)
2. If sudo requires a password, it exits with code 1 and prints a localized error message
3. pyinfra checks for the English error message to detect this condition
4. On non-English systems, the check fails, and pyinfra reports the command as failed instead of prompting for a password

### Secondary Location

**File:** `/Users/nick/Dev/Webroot/pyinfra/src/src/pyinfra/connectors/util.py`
**Function:** `make_unix_command()` (lines 302-405)

This is where the sudo command is constructed. The proposed fix involves modifying this function to set `LANG=C` before the sudo command.

---

## Proposed Fix

### Option 1: Set `LANG=C` Before Sudo (Recommended by Issue Reporter)

Modify the `make_unix_command()` function to prepend `LANG=C` before the sudo command, forcing sudo to output messages in the C/POSIX locale (English).

**Current Code (line 360-361):**
```python
if _sudo:
    command_bits.extend(["sudo", "-H"])
```

**Proposed Change:**
```python
if _sudo:
    command_bits.extend(["LANG=C", "sudo", "-H"])
```

**Pros:**
- Simple fix
- Ensures consistent sudo output across all locales
- Confirmed to work by multiple users (fr_FR, de_DE)

**Cons:**
- The `LANG=C` environment variable may leak into the subcommand when `sudo` preserves the environment
- As noted in the issue comments:
  ```bash
  sudo bash -c 'echo $LANG'
  # => de_DE.UTF-8

  LANG=C sudo bash -c 'echo $LANG'
  # => C
  ```
- This could potentially break scripts that depend on locale-specific output

### Option 2: Use `env` to Isolate LANG Setting

A more targeted approach would be to use `env` to set `LANG=C` only for the sudo process itself:

```python
if _sudo:
    command_bits.extend(["env", "LANG=C", "sudo", "-H"])
```

However, this has similar concerns about environment leakage.

### Option 3: Check for Sudo Exit Code Pattern

Instead of relying on string matching, check for:
1. Exit code 1
2. The presence of "sudo" at the start of the error message
3. No actual command output

This is more fragile but avoids locale issues entirely.

### Option 4: Multiple String Matching (Not Recommended)

Add translations for common locales:

```python
SUDO_PASSWORD_REQUIRED_MESSAGES = [
    "sudo: a password is required",
    "sudo: il est necessaire de saisir un mot de passe",  # French
    "sudo: Ein Passwort ist erforderlich",  # German
    # ... etc
]
```

**Cons:**
- Incomplete coverage
- Maintenance burden
- Still won't handle all locales

---

## Recommended Implementation

### Primary Fix

Modify `/Users/nick/Dev/Webroot/pyinfra/src/src/pyinfra/connectors/util.py`:

**In `make_unix_command()` function (around line 360):**

```python
# Current:
if _sudo:
    command_bits.extend(["sudo", "-H"])

# Change to:
if _sudo:
    # Set LANG=C to ensure consistent sudo output regardless of system locale
    # This is needed for execute_command_with_sudo_retry() to detect password prompts
    command_bits.extend(["LANG=C", "sudo", "-H"])
```

### Test Updates

Modify `/Users/nick/Dev/Webroot/pyinfra/src/tests/test_connectors/test_util.py` to update expected command strings:

```python
def test_sudo_command(self):
    command = make_unix_command("uptime", _sudo=True)
    assert command.get_raw_value() == "LANG=C sudo -H -n sh -c uptime"
```

Similar updates needed for:
- `test_sudo_multi_arg_command`
- `test_sudo_preserve_env_command`
- `test_use_sudo_login_command`
- `test_sudo_user_command`
- `test_mixed_command`

### Test Updates for SSH Tests

Modify `/Users/nick/Dev/Webroot/pyinfra/src/tests/test_connectors/test_ssh.py` to update expected command strings where sudo is used.

---

## Files Requiring Modification

1. **`/Users/nick/Dev/Webroot/pyinfra/src/src/pyinfra/connectors/util.py`**
   - Function: `make_unix_command()` (line ~360)
   - Add `LANG=C` before sudo command

2. **`/Users/nick/Dev/Webroot/pyinfra/src/tests/test_connectors/test_util.py`**
   - Update test assertions to include `LANG=C` prefix

3. **`/Users/nick/Dev/Webroot/pyinfra/src/tests/test_connectors/test_ssh.py`**
   - Update test assertions to include `LANG=C` prefix

---

## Considerations and Risks

### Environment Leakage Concern

As noted by @simonhammes and @Fizzadar in the GitHub comments, setting `LANG=C` may leak into the actual command being executed:

```bash
LANG=C sudo bash -c 'echo $LANG'
# => C (instead of the user's locale)
```

**Mitigation:**
- Document this behavior change
- Consider if `-E` (preserve environment) flag interactions need special handling
- The impact is likely minimal for most infrastructure automation use cases

### Alternative: `LC_ALL=C`

Using `LC_ALL=C` instead of `LANG=C` would be more forceful in overriding locale settings:

```python
command_bits.extend(["LC_ALL=C", "sudo", "-H"])
```

`LC_ALL` takes precedence over all other locale variables, ensuring consistent behavior.

---

## Testing Recommendations

1. **Unit Tests:** Update existing tests to expect `LANG=C` prefix
2. **Integration Tests:** Test with non-English locales (fr_FR, de_DE, es_ES)
3. **Edge Cases:**
   - Test with `_preserve_sudo_env=True`
   - Test with `_sudo_user` specified
   - Test with combined `_sudo` and `_su_user`

---

## References

- GitHub Issue: https://github.com/pyinfra-dev/pyinfra/issues/1125
- Related Discussion: Issue comments discuss the LANG leakage concern
- Affected Locales Confirmed: `fr_FR.UTF-8`, `de_DE.UTF-8`
