zeroclaw-labs/zeroclaw · error
Failed to atomically replace config file: {e}
Error message
Failed to atomically replace config file: {e} What it means
write_config_atomically writes the new config to a temp file, backs up the existing config, then publishes via fs::rename(temp, config). This error fires when that final rename fails; the code first removes the temp file and attempts to restore the backup over the config, so the on-disk config should be back to its pre-save state. The {e} suffix carries the underlying io::Error (permission denied, cross-device link, Windows sharing violation, target is a directory, …). Data is not lost — the previous config is preserved or restorable from the .bak file.
Source
Thrown at crates/zeroclaw-config/src/schema.rs:22993
let had_existing_config = config_path.exists();
if had_existing_config {
fs::copy(config_path, &backup_path).await.with_context(|| {
format!(
"Failed to create config backup before atomic replace: {}",
backup_path.display()
)
})?;
}
if let Err(e) = fs::rename(&temp_path, config_path).await {
let _ = fs::remove_file(&temp_path).await;
if had_existing_config && backup_path.exists() {
fs::copy(&backup_path, config_path)
.await
.context("Failed to restore config backup")?;
}
anyhow::bail!("Failed to atomically replace config file: {e}");
}
#[cfg(unix)]
{
use std::{fs::Permissions, os::unix::fs::PermissionsExt};
if let Err(err) = fs::set_permissions(config_path, Permissions::from_mode(0o600)).await {
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
.with_outcome(::zeroclaw_log::EventOutcome::Unknown),
&format!(
"Failed to harden config permissions to 0600 at {}: {}",
config_path.display().to_string(),
err
)
);
}
}View on GitHub (pinned to 88bb9c8533)
Solutions
- Check the {e} suffix: 'permission denied' → fix ownership/permissions of the config path and its parent dir; 'cross-device link' → move temp creation onto the same filesystem (keep config_path local); sharing violation → close editors/AV/sync tools touching the file
- Verify free space on the filesystem holding the config
- If a .bak file remains next to the config, compare/restore it manually: cp config.toml.bak config.toml
- Retry the save once the locking process is gone (the previous config is intact, so this is safe)
Defensive patterns
Strategy: retry
Validate before calling
// preflight before save():
async fn can_replace(path: &Path) -> bool {
let parent = match path.parent() { Some(p) => p, None => return false };
std::fs::create_dir_all(parent).is_ok()
&& path.is_file() || !path.exists() // not a directory
&& has_write_access(parent) // e.g. try File::create in parent
} Try / catch
let mut attempt = 0;
loop {
attempt += 1;
match cfg.save().await {
Ok(()) => break,
Err(e) if attempt < 3 && e.to_string().contains("Failed to atomically replace config file") => {
tokio::time::sleep(std::time::Duration::from_millis(250 * attempt as u64)).await; // Windows lock may clear
}
Err(e) => {
eprintln!("config save failed after restore; a .bak may exist: {e:#}");
break;
}
}
} Prevention
- Keep the config directory out of synced folders (Dropbox/OneDrive) and off network mounts
- Don't hold config.toml open in other programs while the app runs auto-save
- After any failed save, check for config.toml.bak and restore it before the next start
- Ensure the process user owns the config directory and there is free disk space
When it happens
Trigger: Another process holds the config file open with an exclusive lock on Windows (editor, antivirus, sync client like Dropbox/OneDrive) when save()/save_dirty() runs; config_path is on a read-only or full filesystem; the rename crosses a filesystem boundary because the temp file's parent differs from the target's; config_path is actually a directory; insufficient permissions on the parent directory.
Common situations: Config auto-save while the user has config.toml open in Excel/locked editor on Windows; config directory inside a synced folder where the sync agent races renames; containers where the config dir is a mounted volume with odd rename semantics; running as a user without write permission on ~/.zeroclaw.
Related errors
- at least one path must be configured
- path '{path}' is a broad system root; set allow_broad_roots
- event '{kind}' is invalid; expected created, modified, delet
- unsupported room visibility '{other}': expected private or p
- channel '{}' does not support forge API requests
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/b5b8c9ecbbdab9af.
Report an issue: GitHub.