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
- Run sudo -v first in an interactive terminal so credentials are cached, then retry the save.
- Add a sudoers rule allowing tee for this user (e.g. user ALL=(root) NOPASSWD: /usr/bin/tee) or use a persistence helper.
- 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
- Cache sudo credentials with `sudo -v` before saving
- Configure NOPASSWD sudoers entry for tee if using elevated saves routinely
- Read the stderr in the error message to distinguish password, policy, and filesystem causes
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
- SudoSaveRequired
- sudo tee failed
- cannot reach the Fresh editor for session
- could not read script
- Failed to read
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)