sxyazi/yazi · error · anyhow::Error

Failed to join new name with parent directory

Error message

Failed to join new name with parent directory

What it means

Thrown by TryFrom<&Data> for File (yazi-fs/src/file/data.rs:21) when Data::as_any::<File>() fails on a borrowed read: the argument exists but is not a Data::Any containing a File (as_any returns None for every other variant, then .cloned() cannot produce a File). This borrowed path is what Action::get-style read accessors use, so it fires when a handler reads a File-typed argument by reference and the value has a different shape.

Source

Thrown at yazi-actor/src/mgr/rename.rs:55

				.position(|c| c == '.')
				.filter(|_| hovered.is_file())
				.map(|i| name.chars().count() - i - 1)
				.filter(|&i| i != 0),
			_ => None,
		};

		let (tab, old) = (cx.tab().id, hovered.url_owned());
		let mut input =
			input!(cx, YAZI.input.rename(hovered.is_dir()).with_value(name).with_cursor(cursor))?;

		tokio::spawn(async move {
			let Some(InputEvent::Submit(name)) = input.recv().await else { return Ok(()) };
			if name.is_empty() {
				return Ok(());
			}

			let Some(Ok(new)) = old.parent().map(|u| u.try_join(name)) else {
				bail!("Failed to join new name with parent directory");
			};

			if form.force || Self::try_ask(&old, &new).await? {
				Self::r#do(tab, old, new).await?;
			}
			Ok::<(), anyhow::Error>(())
		});
		succ!();
	}
}

impl Rename {
	async fn r#do(tab: Id, old: UrlBuf, new: UrlBuf) -> Result<()> {
		let Some((old_t, old_k)) = old.pair() else { return Ok(()) };
		let Some(_) = new.pair() else { return Ok(()) };
		let _permit = WATCHER.acquire().await.unwrap();

		let overwritten = engine::casefold(&new).await;

View on GitHub (pinned to 441b332de8)

Solutions

  1. Send a real File object in that argument slot (hovered/entry userdata), not a path string or table
  2. Use Files when multiple entries are involved
  3. Type-check on the Lua side before emitting and fail fast with a clear message
  4. Match plugin and yazi versions so the Any payloads have identical concrete types

Example fix

-- before
local f = ya.emit("cmd", { entry = url })
-- after
ya.emit("cmd", { entry = hovered_file })
Defensive patterns

Strategy: type-guard

Validate before calling

// Rust: probe before converting
if a.any::<File>("entry").is_some() { /* safe to get::<File> */ }

Type guard

// Rust
fn as_file(data: &Data) -> Option<File> { data.as_any::<File>().cloned() }

Try / catch

// Rust
let file = a.get::<File>("entry").map_err(|e| tracing::warn!("bad entry arg: {e:#}"));

Prevention

When it happens

Trigger: A form or plugin callback calling get::<File>(name) where the argument holds a String/Integer/Table or an Any of another concrete type (Files, Folder, SpotLock, etc.); forwarding an argument slot between commands with its type changed along the way.

Common situations: Same as the owned flavor: passing URLs or plain tables instead of File userdata; argument slots reused across commands with different types after refactors; plugin API misuse after docs/examples drift from the binary version.

Related errors


AI-assisted analysis of sxyazi/yazi@441b332de8 (2026-08-19). Data as JSON: /api/errors/12b2433d808c6e3e. Report an issue: GitHub.