sinelaw/fresh · error · RuntimeError

sudo tee failed

Error message

sudo tee failed: {stderr.decode().strip()}

What it means

cmd_sudo_write writes file contents on a remote host by piping data into `sudo tee <path>`. When the sudo/tee process exits non-zero, the helper raises RuntimeError embedding tee's stderr so the editor's remote-write operation fails loudly instead of silently leaving the file unwritten. It usually means sudo could not complete the write: missing sudo credentials, non-interactive sudo, bad path, or filesystem errors.

Solutions

  1. Read the stderr embedded in the message to see the actual sudo/tee failure cause (password prompt, permission, no such file).
  2. Enable passwordless or cached sudo for the agent user (configure NOPASSWD in sudoers, or run sudo -v first to cache credentials).
  3. Run the agent over a connection that allows sudo (allocate a TTY, or use `sudo -n` in an environment with cached credentials).
  4. Verify the target path exists/is writable by root (parent directory present, filesystem not read-only or full).

Example fix

// before: failing because sudo cannot prompt for a password over a non-TTY channel
raise RuntimeError(f"sudo tee failed: sudo: a terminal is required...")

// after: grant the agent user passwordless tee for the target, or pre-cache credentials
# /etc/sudoers.d/agent
agent ALL=(root) NOPASSWD: /usr/bin/tee
# or, before editing: sudo -v
Defensive patterns

Strategy: try-catch

Validate before calling

import subprocess
def sudo_available(path):
    r = subprocess.run(["sudo", "-n", "true"], capture_output=True)
    return r.returncode == 0
# also check: parent dir exists and sudo -n works before cmd_sudo_write

Try / catch

try:
    result = sudo_write(path, data)
except RuntimeError as e:
    if "terminal is required" in str(e) or "a password is required" in str(e):
        # prompt for credentials / run sudo -v, then retry
        ...
    else:
        log.error("sudo tee failed: %s", e)

Prevention

When it happens

Trigger: Calling the remote sudo-write RPC (cmd_sudo_write in agent.py) when the spawned `sudo tee` process returns a non-zero exit code — e.g. sudo requires a password but no TTY/askpass is available (`sudo: a terminal is required`), the user is not in sudoers, the target path's directory doesn't exist or is unwritable, or the disk is full/read-only.

Common situations: Editing root-owned config files (e.g. /etc/...) over the remote agent session where the SSH connection has no TTY allocated, so sudo cannot prompt; sudo timeout expired mid-session; NOPASSWD not configured for the agent user; path typos or the file's parent directory was removed.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13). Data as JSON: /api/errors/8cbf3a525ea8a3e6. Report an issue: GitHub.

Appendix: source

Thrown at crates/fresh-editor/src/services/remote/agent.py:137

    path = validate_path(p["path"])
    data = unb64(p["data"])

    # Get original metadata to preserve permissions
    mode = p.get("mode")
    uid = p.get("uid")
    gid = p.get("gid")

    # Use sudo tee to write the file
    proc = subprocess.Popen(
        ["sudo", "tee", path],
        stdin=subprocess.PIPE,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.PIPE,
    )
    _, stderr = proc.communicate(data)

    if proc.returncode != 0:
        raise RuntimeError(f"sudo tee failed: {stderr.decode().strip()}")

    # Restore permissions and ownership if provided
    if mode is not None:
        subprocess.run(["sudo", "chmod", f"{mode:o}", path], check=True,
                       capture_output=True)
    if uid is not None and gid is not None:
        subprocess.run(["sudo", "chown", f"{uid}:{gid}", path], check=True,
                       capture_output=True)

    send(id, r={"size": len(data)})


def cmd_stat(id, p):
    """Get file/directory metadata."""
    path = validate_path(p["path"])
    follow = p.get("link", True)

    st = os.stat(path, follow_symlinks=follow)

View on GitHub (pinned to 67894ca546)