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

  1. 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)
  2. Close processes locking the target file (antivirus, file-sync, the running core) and retry
  3. Free disk space / fix quota if the error indicates ENOSPC
  4. 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

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


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)