libnyanpasu/clash-nyanpasu · error
failed to promote runtime config
Error message
failed to promote runtime config: {error} What it means
`write_product` promotes the rendered runtime config to its final path using the `atomicwrites` crate inside `spawn_blocking`; if the atomic write fails (I/O error from create/rename/fsync), the error is wrapped with this message. The promotion step is the last, all-or-nothing step of publishing a runtime config.
Solutions
- Check the wrapped `{error}` message and verify the product directory exists and is writable (create it: the tests `write_product_creates_the_runtime_directory` show it is expected to be created)
- Close processes locking the target file (antivirus, file-sync, the running core) and retry
- Free disk space / fix quota if the error indicates ENOSPC
- Run the app with sufficient permissions for the config directory, or move the config dir out of a protected location
Example fix
// before std::fs::remove_dir_all(runtime_dir).ok(); // deletes dir while writer expects it or its parent write_product(...).await?; // after std::fs::create_dir_all(runtime_dir)?; // ensure promotion target parent exists write_product(...).await?;
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check promotion target writability
let dir = product_path.parent().ok_or("no parent dir")?;
std::fs::create_dir_all(dir)?;
let probe = dir.join(".write-probe");
std::fs::write(&probe, b"")?;
std::fs::remove_file(&probe)?; Try / catch
match write_product(&product, bytes).await {
Err(e) if e.to_string().contains("failed to promote runtime config") => {
// inspect source error: check disk space, file locks (antivirus/sync/core process), permissions
eprintln!("runtime config promotion failed: {e}");
}
Err(e) => return Err(e),
Ok(()) => (),
} Prevention
- Ensure the runtime config directory exists before committing
- Exclude the config directory from antivirus real-time locking and file-sync tools
- Avoid deleting or moving the runtime directory while the app is running
- Keep adequate free disk space; handle ENOSPC explicitly
When it happens
Trigger: Calling the runtime config build/commit path that reaches `write_product` (backend/tauri/src/client/runtime.rs:118) when the target directory does not exist or is not writable, the disk is full, an antivirus/handle holds the file open, or a permission error occurs on rename.
Common situations: Runtime config directory deleted or locked by another process (editor, sync client like OneDrive/Dropbox, another app instance); read-only filesystem; disk quota exceeded; Windows file locking by the mihomo core process reading the previous config.
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 repair typed clash config before split_legacy_config…
- config path is not UTF-8
- failed to open storage
- failed to parse config
- failed to parse config
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/f8f0db85eb2705f0.
Report an issue: GitHub.
Appendix: source
Thrown at backend/tauri/src/client/runtime.rs:118
}
#[derive(Debug, Clone, Default)]
pub struct RuntimeLifecycleState {
pub promoted: Option<Arc<RuntimeSnapshot>>,
}
pub(crate) async fn write_product(product: &Path, bytes: &[u8]) -> anyhow::Result<()> {
if let Some(parent) = product.parent() {
tokio::fs::create_dir_all(parent).await?;
}
let product = product.to_path_buf();
let bytes = bytes.to_vec();
tokio::task::spawn_blocking(move || {
atomicwrites::AtomicFile::new(&product, atomicwrites::OverwriteBehavior::AllowOverwrite)
.write(|file| std::io::Write::write_all(file, &bytes))
})
.await?
.map_err(|error| anyhow::anyhow!("failed to promote runtime config: {error}"))?;
Ok(())
}
#[derive(Debug, Clone)]
pub struct RuntimePaths {
product: Utf8PathBuf,
candidate_dir: Utf8PathBuf,
}
impl RuntimePaths {
pub fn from_resolver(paths: &PathResolver) -> anyhow::Result<Self> {
let runtime_dir = utf8_path(paths.app_config_dir().join(RUNTIME_CONFIG_DIR))?;
Ok(Self {
product: runtime_dir.join(RUNTIME_CONFIG),
candidate_dir: runtime_dir.join(".candidates"),
})
}
View on GitHub (pinned to f7dbce2997)