astrid-runtime/astrid · critical
{detail}: {error}
Error message
{detail}: {error} What it means
After staging, the installer renames each `.{name}.new` onto its live path. If a rename fails, the function rolls back already-installed files from `.bak` copies; this error wraps both the rename failure and the final detail message. If rollback also failed, the detail reads `failed to install <live>; rollback also failed (...); restore *.bak manually` — the installation may be in a mixed state and the user must restore backups by hand.
Solutions
- Stop all running instances of the executable (or update a copy not currently executing) and retry the update
- Manually restore each `<name>.bak` over `<name>` in `install_dir` as the message instructs, then retry the update
- Check and remove immutable flags / policy blocks (`chattr -i`, SELinux/AppArmor) on install_dir
- Serialize updater runs with a lock file to avoid concurrent replacement attempts
Example fix
// before
app.update_self()?; // app is currently running -> ETXTBSY
// after
if !current_exe_is_running_copy() {
app.update_self()?;
} else {
spawn_detached_updater_and_exit();
} Defensive patterns
Strategy: try-catch
Validate before calling
fn live_executables_idle(install_dir: &Path, names: &[&str]) -> bool {
!install_dir.join(".running.lock").exists()
&& names.iter().all(|n| is_replaceable(&install_dir.join(n)))
}
fn is_replaceable(p: &Path) -> bool {
!p.exists() || std::fs::rename(p, p).is_ok()
} Try / catch
match replace_executable_set(&install_dir, &extract_dir, names) {
Err(e) if e.to_string().contains("rollback also failed") => {
eprintln!("MANUAL ACTION REQUIRED: restore <name>.bak over <name> in {}", install_dir.display());
std::process::exit(1);
}
Err(e) => return Err(e.into()),
Ok(()) => {},
} Prevention
- Never self-replace the exact binary that is executing; spawn a detached updater and exit first
- Serialize update runs with an exclusive lock file
- Keep .bak files until a successful post-update verification
- Check for immutable flags / restrictive policies on install_dir before updating
When it happens
Trigger: `std::fs::rename(temporary, live)` fails — typically ETXTBSY (live executable still running on Linux), EACCES/EPERM (no permission to replace in install_dir), or the live target is held/immutable; rollback then also fails when `.bak` copies are missing, locked, or the directory became read-only.
Common situations: Self-updating a running CLI (ETXTBSY on Linux); another updater instance running concurrently; SELinux/AppArmor or read-only mounts blocking the rename; `.bak` files deleted by cleanup tools so rollback cannot restore.
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
- cannot resolve install directory for
- executable replacement directories must exist
- failed to stage
- is not writable — re-run with elevated permissions, or…
- release archive is missing
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/6c3a2c068a985428.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-core/src/platform_fs.rs:937
.push(format!("{}: {rollback_error}", installed_live.display()));
}
} else if let Err(rollback_error) = std::fs::remove_file(installed_live) {
rollback_errors.push(format!("{}: {rollback_error}", installed_live.display()));
}
}
for (remaining, _) in &staged[index..] {
let _ = std::fs::remove_file(remaining);
}
let detail = if rollback_errors.is_empty() {
format!("failed to install {}", live.display())
} else {
format!(
"failed to install {}; rollback also failed ({}); restore *.bak manually",
live.display(),
rollback_errors.join("; ")
)
};
return Err(io::Error::new(error.kind(), format!("{detail}: {error}")));
}
}
Ok(())
}
#[cfg(test)]
#[path = "platform_fs/tests.rs"]
mod tests;
View on GitHub (pinned to affd8760f4)