libnyanpasu/clash-nyanpasu · error
destination path has no file name: {}
Error message
destination path has no file name: {} What it means
`atomic_write` performs a crash-safe write by creating a temp file in a `.atomicwrite` directory under the target's parent and renaming it onto `path`. A rename/replace target must be an actual file name, so the guard rejects any path whose `file_name()` is None. This happens for paths that end in a separator, are a filesystem root, or are otherwise just a directory path.
Source
Thrown at backend/tauri/src/core/migration/fs.rs:21
//! Wraps [`atomicwrites`] (already a workspace dependency via `nyanpasu-core`)
//! behind a single helper so the migration store and every config rewrite share
//! one durable write path. Keeping the third-party type in one place means a
//! future swap only touches this file.
use anyhow::{Context, ensure};
use atomicwrites::{AllowOverwrite, AtomicFile};
use std::{io::Write, path::Path};
/// Atomically write `contents` to `path`.
///
/// The destination is never left half-written: `atomicwrites` writes the bytes
/// into a temp file under a randomized `.atomicwrite` subdirectory of the
/// target's parent, fsyncs it, then atomically replaces `path`. On Unix it also
/// fsyncs the parent directories so the rename survives a crash; on Windows it
/// replaces via `MoveFileExW` with write-through semantics. Missing parent
/// directories are created first.
pub(crate) fn atomic_write(path: &Path, contents: &[u8]) -> anyhow::Result<()> {
ensure!(
path.file_name().is_some(),
"destination path has no file name: {}",
path.display()
);
if let Some(parent) = path
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
{
std::fs::create_dir_all(parent)
.with_context(|| format!("failed to create dir {}", parent.display()))?;
}
AtomicFile::new(path, AllowOverwrite)
.write(|file| file.write_all(contents))
.with_context(|| format!("failed to atomically write {}", path.display()))?;
Ok(())
}
View on GitHub (pinned to f7dbce2997)
Solutions
- Append the intended file name to the target path before calling `atomic_write`
- Strip trailing separators from user- or env-supplied paths (use `PathBuf::set_extension` or push the file name explicitly)
- Check `path.file_name().is_some()` at the call site and log the full path to find where the path lost its last component
Example fix
// before
let dir = config_dir(); // e.g. "/home/u/.config/nyanpasu/"
atomic_write(Path::new(&dir), &bytes)?;
// after
let path = config_dir().join("verge.yaml");
atomic_write(&path, &bytes)?; Defensive patterns
Strategy: validation
Validate before calling
fn ensure_writable_file_path(path: &std::path::Path) -> anyhow::Result<()> {
anyhow::ensure!(
path.file_name().is_some(),
"atomic_write needs a file path, got: {}",
path.display()
);
Ok(())
} Prevention
- Always build target paths with `dir.join("file.yaml")`, never by string concatenation that can leave a trailing separator
- Strip trailing '/' or '\\' from user/env-supplied paths before constructing PathBuf
- Keep a helper that normalizes destination paths once instead of ad-hoc path math at call sites
- Assert `file_name().is_some()` in tests for every path that reaches `atomic_write`
When it happens
Trigger: Calling `fs::atomic_write(path, contents)` with a path such as `/`, `C:\`, `some/dir/` (trailing slash), or any `Path` built without a final file component — `path.file_name()` returns `None` and the `ensure!` fires with the formatted path.
Common situations: Passing a directory instead of a file path; concatenating paths so a trailing separator survives (e.g. from user input or an env var); resolving a config path to its root/parent by mistake.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- path is not valid UTF-8: {}
- failed to get file stem
- failed to get app_path
- failed to get core dir
- failed to allocate a unique runtime candidate after 16 attem
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/c896710ce857e1e1.
Report an issue: GitHub.