sxyazi/yazi · error · io::Error

empty file stem

Error message

empty file stem

What it means

`_unique_file` generates a non-colliding filename (e.g. `name (1).ext`) for copy/paste or download dedup, and it requires the URL's file stem to be non-empty. If `url.stem()` yields None — a path ending in a separator, a dot-only tail, or otherwise empty stem — the function aborts with InvalidInput instead of inventing a name.

Source

Thrown at yazi-vfs/src/fns.rs:28

		Ok(_) => true,
		Err(e) => e.kind() != io::ErrorKind::NotFound,
	}
}

pub async fn unique_file(u: UrlBuf, is_dir: bool) -> io::Result<UrlBuf> {
	let result =
		if is_dir { engine::create_dir(&u).await } else { engine::create_new(&u).await.map(|_| ()) };

	match result {
		Ok(()) => Ok(u),
		Err(e) if e.kind() == io::ErrorKind::AlreadyExists => _unique_file(u, is_dir).await,
		Err(e) => Err(e),
	}
}

async fn _unique_file(mut url: UrlBuf, is_dir: bool) -> io::Result<UrlBuf> {
	let Some(stem) = url.stem().owned() else {
		return Err(io::Error::new(io::ErrorKind::InvalidInput, "empty file stem"));
	};

	let dot_ext = match url.ext() {
		Some(e) => {
			let mut s = StrandBuf::with_capacity(url.loc().kind(), e.len() + 1);
			s.push_str(".");
			s.try_push(e)?;
			s
		}
		None => StrandBuf::default(),
	};

	let mut name = StrandBuf::with_capacity(url.loc().kind(), stem.len() + dot_ext.len() + 5);
	for i in 1u64.. {
		name.clear();
		name.try_push(&stem)?;

		if is_dir {

View on GitHub (pinned to 8ebf930f17)

Solutions

  1. Strip trailing separators and ensure the URL has a real file name before calling unique_file
  2. If the source has no name, synthesize one (e.g. 'download', 'folder') before invoking the API
  3. Validate with `url.file_name()`/stem on the caller side and return a clear user-facing error otherwise
  4. Check upstream producers (clipboard, download job) for why the name is empty

Example fix

-- before
local unique = vfs.unique_file(url) -- url ends with '/'
-- after
if not url:stem() then url = url:join('unnamed') end
local unique = vfs.unique_file(url)
Defensive patterns

Strategy: validation

Validate before calling

local tail = url:name() or url:urn()
assert(tail ~= '' and tail ~= '/' and url:stem(), 'unique_file needs a non-empty file stem, got: '..tostring(tail))

Try / catch

local ok, unique = pcall(function() return vfs.unique_file(url, is_dir) end)
if not ok then
  -- 'empty file stem' (InvalidInput): synthesize a name and retry
  url = url:join('unnamed')
  unique = vfs.unique_file(url, is_dir)
end

Prevention

When it happens

Trigger: Calling `unique_file(url, is_dir)` with a URL whose tail has no stem: a trailing slash (directory tail without a name), a name consisting only of an extension separator, or an empty last component produced by joining paths programmatically.

Common situations: Pasting/downloads where the remote name was empty or slash-terminated, drag-drop payloads with bare directory URLs, or code constructing `dir.join("")` and passing the result to unique_file.

Related errors


AI-assisted analysis of sxyazi/yazi@8ebf930f17 (2026-09-02). Data as JSON: /api/errors/f6a63e7eb66b821e. Report an issue: GitHub.