sinelaw/fresh · error · io::Error (PermissionDenied)

sudo tee failed

Error message

sudo tee failed: {}

What it means

The privileged write path (save via `sudo tee`) fails when the spawned sudo tee process exits with non-zero status. The error carries the trimmed stderr from sudo/tee so the actual cause (password prompt failure, policy denial, unreadable path) is visible. Returned as PermissionDenied.

Solutions

  1. Run sudo -v first in an interactive terminal so credentials are cached, then retry the save.
  2. Add a sudoers rule allowing tee for this user (e.g. user ALL=(root) NOPASSWD: /usr/bin/tee) or use a persistence helper.
  3. Check the captured stderr in the message; if it's a read-only mount or 'Operation not permitted', fix filesystem permissions/attributes instead.

Example fix

// before (non-interactive sudo fails with 'a password is required')
sudo tee /etc/nginx/nginx.conf
// after: cache credentials first, or grant NOPASSWD
sudo -v && echo '...' | sudo tee /etc/nginx/nginx.conf
Defensive patterns

Strategy: retry

Validate before calling

// check sudo availability before attempting elevated save
if !std::process::Command::new("sudo").arg("-n").arg("true").status().map(|s| s.success()).unwrap_or(false) {
    eprintln!("sudo requires a password; run `sudo -v` first");
}

Try / catch

match save_with_sudo(path, data) {
    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
    eprintln!("elevated save failed: {e}; run `sudo -v` and retry");
    // retry once after credential refresh
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling the elevated-save path when sudo requires a password on a non-tty, the user lacks sudo rights for the target, or the target file/directory is not writable even by root (read-only mount, immutable flag).

Common situations: Saving a root-owned file over SSH without a tty; sudoers NOPASSWD not configured for tee; editing files on a read-only filesystem.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at crates/fresh-editor-core/src/model/filesystem.rs:1528

        // Write data via sudo tee
        let mut child = Command::new("sudo")
            .args(["tee", &path.to_string_lossy()])
            .stdin(Stdio::piped())
            .stdout(Stdio::null())
            .stderr(Stdio::piped())
            .hide_window()
            .spawn()
            .map_err(|e| io::Error::other(format!("failed to spawn sudo: {}", e)))?;

        if let Some(mut stdin) = child.stdin.take() {
            use std::io::Write;
            stdin.write_all(data)?;
        }

        let output = child.wait_with_output()?;
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            return Err(io::Error::new(
                io::ErrorKind::PermissionDenied,
                format!("sudo tee failed: {}", stderr.trim()),
            ));
        }

        // Set permissions via sudo chmod
        let status = Command::new("sudo")
            .args(["chmod", &format!("{:o}", mode), &path.to_string_lossy()])
            .hide_window()
            .status()?;
        if !status.success() {
            return Err(io::Error::other("sudo chmod failed"));
        }

        // Set ownership via sudo chown
        let status = Command::new("sudo")
            .args([
                "chown",

View on GitHub (pinned to 67894ca546)