sxyazi/yazi · error · io::Error

Not a local URL: {url:?}

Error message

Not a local URL: {url:?}

What it means

`Local::new(url)` (yazi-fs/src/engine/local/local.rs:91) constructs a local-filesystem handle only from `Url::Regular` and `Url::Search` variants. Any other variant (`Mount`, `Hub`, `Scope`, `Sftp`) is rejected with `ErrorKind::InvalidInput` and the message `Not a local URL: {url:?}`. The constructor is the single gatekeeper that keeps non-local URLs out of the local engine.

Source

Thrown at yazi-fs/src/engine/local/local.rs:91

	where
		P: DynPath,
	{
		let to = to.dyn_path().as_os()?;

		tokio::fs::hard_link(self.path, to).await
	}

	#[inline]
	async fn metadata(&self) -> io::Result<Cha> {
		Ok(Cha::new(self.path.file_name().unwrap_or_default(), tokio::fs::metadata(self.path).await?))
	}

	#[inline]
	async fn new<'b>(url: Url<'b>) -> io::Result<Self::Me<'b>> {
		match url {
			Url::Regular(loc) | Url::Search { loc, .. } => Ok(Self::Me { url, path: loc.as_inner() }),
			Url::Mount { .. } | Url::Hub { .. } | Url::Scope { .. } | Url::Sftp { .. } => {
				Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not a local URL: {url:?}")))
			}
		}
	}

	#[inline]
	async fn read_dir(self) -> io::Result<Self::ReadDir> {
		Ok(match self.url.kind() {
			AuthKind::Regular => Self::ReadDir::Regular(tokio::fs::read_dir(self.path).await?),
			AuthKind::Search => Self::ReadDir::Others {
				reader: tokio::fs::read_dir(self.path).await?,
				dir:    Arc::new(self.url.to_owned()),
			},
			AuthKind::Mount | AuthKind::Hub | AuthKind::Scope | AuthKind::Sftp => Err(io::Error::new(
				io::ErrorKind::InvalidInput,
				format!("Not a local URL: {:?}", self.url),
			))?,
		})
	}

View on GitHub (pinned to 94abcfa92f)

Solutions

  1. Match on the URL before constructing: handle `Url::Regular(loc) | Url::Search { loc, .. }` with the local engine and send other kinds to their own backend.
  2. Check `url.kind()` against `AuthKind::Regular | AuthKind::Search` as a pre-condition.
  3. Fix the upstream URL construction so only local paths reach this code.

Example fix

// before
let local = Local::new(url).await?; // Err for Url::Sftp

// after
match url {
    Url::Regular(loc) | Url::Search { loc, .. } => { let local = Local::new(url).await?; /* ... */ }
    _ => { /* use the engine for Mount/Hub/Scope/Sftp */ }
}
Defensive patterns

Strategy: type-guard

Validate before calling

use yazi_fs::engine::AuthKind;

if !matches!(url.kind(), AuthKind::Regular | AuthKind::Search) {
    // do not construct Local from this URL
}

Type guard

fn accepts_local(url: &Url) -> bool {
    matches!(url, Url::Regular(_) | Url::Search { .. })
}

Try / catch

match Local::new(url).await {
    Ok(me) => { /* ... */ }
    Err(e) if e.kind() == io::ErrorKind::InvalidInput => { /* re-dispatch by url.kind() */ }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `Local::new(url)` (or `read_dir`/metadata helpers that build a `Local` first) with a `Url` parsed from `sftp://…`, `mount://…`, a hub/scope URL, or a test URL like `test-hub://a1/@root/a`.

Common situations: A plugin or test iterates URLs of mixed kinds and feeds each to the local engine without filtering; a config or script embeds a remote URL string that parses into a non-`Regular` variant.

Related errors


AI-assisted analysis of sxyazi/yazi@94abcfa92f (2026-08-16). Data as JSON: /api/errors/6b2da7e3536b50b8. Report an issue: GitHub.