Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ and `ug revert` return to `--global`.
`--user` also fails for a workspace that publishes a managed config, which must be installed
machine-wide; use `--global` for it.

A wrapper that starts `ug` in a terminal nobody watches (an unattended relaunch) can set
`UG_NONINTERACTIVE=1`: sudo then never prompts for a password, as when no terminal is attached, and
a launch whose managed file can't be updated continues with the per-user settings when they are
compatible.

### Extra headers for one session

```bash
Expand Down
12 changes: 12 additions & 0 deletions src/ucode/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,15 @@

def env_bool(name: str) -> bool:
return os.environ.get(name, "").strip().lower() in TRUE_ENV_VALUES


# Set by a wrapper that starts ug in a terminal nobody watches (e.g. an unattended relaunch).
NONINTERACTIVE_ENV = "UG_NONINTERACTIVE"


def unattended_terminal() -> bool:
"""Whether a wrapper marked this run's terminal as one nobody is watching.

Prompts there (a sudo password, a browser sign-in) would block until the wrapper gives up.
"""
return env_bool(NONINTERACTIVE_ENV)
9 changes: 6 additions & 3 deletions src/ucode/managed_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from typing import Any, cast

from ucode.config_io import APP_DIR, is_dry_run
from ucode.env import env_bool
from ucode.env import env_bool, unattended_terminal
from ucode.os_compatibility import subprocess_cross_os, system_cross_os
from ucode.ui import console, print_note, print_success, print_warning

Expand Down Expand Up @@ -222,8 +222,11 @@ def managed_file_scope(state: dict, tool: str) -> str:


def _sudo_may_prompt() -> bool:
"""A terminal is attached, so sudo can prompt and the persistent worker is usable."""
return current_os() is not OS.WINDOWS and sys.stdin.isatty()
"""A person is at an attached terminal, so sudo can prompt and the persistent worker is usable.

``UG_NONINTERACTIVE`` marks a terminal nobody watches: a password prompt there would block the
launch until its caller gives up."""
return current_os() is not OS.WINDOWS and sys.stdin.isatty() and not unattended_terminal()


def managed_writes_allowed(*, repair_existing: bool = False) -> bool:
Expand Down
20 changes: 20 additions & 0 deletions tests/test_managed_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import ucode.codex_config as codex_config
import ucode.config_io as config_io
from ucode import managed_files
from ucode.env import NONINTERACTIVE_ENV

_REAL_SUDO_REPLACE = managed_files._sudo_replace
# Captured before the autouse ``_supported`` fixture pins the module attribute to True.
Expand Down Expand Up @@ -1686,3 +1687,22 @@ def test_sudo_command_derives_prompt_policy(monkeypatch, interactive):

expected = ["/usr/bin/sudo", *([] if interactive else ["-n"]), "cp", "a", "b"]
assert managed_files._sudo_command("cp", "a", "b") == expected


@pytest.mark.parametrize("value", ["1", "true"])
def test_noninteractive_env_never_lets_sudo_prompt(monkeypatch, value):
"""A wrapper's unattended terminal gets sudo's non-prompting mode, as if no terminal were attached."""
monkeypatch.setattr(managed_files.sys.stdin, "isatty", lambda: True)
monkeypatch.setenv(NONINTERACTIVE_ENV, value)

assert managed_files._sudo_may_prompt() is False
assert managed_files.managed_writes_allowed() is False
assert managed_files.managed_writes_allowed(repair_existing=True) is True
assert managed_files._sudo_command("cp", "a", "b") == ["/usr/bin/sudo", "-n", "cp", "a", "b"]


def test_noninteractive_env_unset_keeps_the_interactive_prompt(monkeypatch):
monkeypatch.setattr(managed_files.sys.stdin, "isatty", lambda: True)
monkeypatch.delenv(NONINTERACTIVE_ENV, raising=False)

assert managed_files._sudo_may_prompt() is True
Loading