sxyazi/yazi · error · io::Error
Not a SFTP URL: {url}
Error message
Not a SFTP URL: {url} What it means
SftpFs::new requires the URL to be the Unix variant carrying auth (`Url::Unix { loc, auth }`), which is how SFTP locations are represented. Any other URL shape (local OS/Unix-without-auth) is rejected with "Not a SFTP URL". The error mirrors the local engine's guard but in reverse.
Source
Thrown at yazi-vfs/src/engine/sftp/sftp.rs:124
}
async fn hard_link<P>(&self, to: P) -> io::Result<()>
where
P: DynPath,
{
let to = to.dyn_path().as_unix()?;
Ok(self.op().await?.hardlink(self.path, to).await?)
}
async fn metadata(&self) -> io::Result<yazi_fs::cha::Cha> {
let attrs = self.op().await?.stat(self.path).await?;
Ok(Cha::try_from((self.path.file_name().unwrap_or_default(), &attrs))?.0)
}
async fn new<'b>(url: Url<'b>) -> io::Result<Self::Me<'b>> {
let Url::Unix { loc, auth } = url else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not a SFTP URL: {url}")));
};
let config: Arc<ServiceSftp> = Vfs::service(auth)?;
let pool = Conn::pool(config.clone());
Ok(Self::Me { url, path: loc.as_inner(), config, pool })
}
async fn read_dir(self) -> io::Result<Self::ReadDir> {
Ok(Self::ReadDir {
dir: Arc::new(self.url.to_owned()),
reader: self.op().await?.read_dir(self.path).await?,
})
}
async fn read_link(&self) -> io::Result<PathBufDyn> {
Ok(self.op().await?.readlink(self.path).await?.into())
}
View on GitHub (pinned to 8ebf930f17)
Solutions
- Check the URL variant is `Url::Unix` with auth before using the SFTP engine; use the local engine for local URLs.
- If the target is remote, construct the URL with its SFTP auth intact (don't strip auth via `physical()`/as_local conversions).
- Route by scheme: local -> yazi-fs local engine, ssh/sftp -> yazi-vfs SftpFs.
Example fix
// before
let file = SftpFs::File::new(url).await?; // url is local
// after
match &url {
Url::Unix { auth, .. } if !auth.is_empty() => SftpFs::File::new(url).await?,
_ => LocalFs::File::new(url).await?,
} Defensive patterns
Strategy: type-guard
Validate before calling
let is_sftp = matches!(url, Url::Unix { .. } if url.auth().map_or(false, |a| !a.is_empty()));
if !is_sftp { bail!("SftpFs requires a Unix URL with auth"); } Type guard
fn as_sftp(url: &Url) -> Option<(&Loc, &AuthArc)> {
match url { Url::Unix { loc, auth } if !auth.is_empty() => Some((loc, auth)), _ => None }
} Try / catch
// io::Error with InvalidInput kind
match SftpFs::File::new(url).await {
Err(e) if e.kind() == io::ErrorKind::InvalidInput => return LocalFs::File::new(url).await.map_err(Into::into),
other => other.map_err(Into::into),
} Prevention
- Only pass URLs whose auth component is intact to the SFTP engine.
- Avoid stripping auth via as_local()/physical() before remote operations.
- Route by scheme so local URLs never reach SftpFs and vice versa.
When it happens
Trigger: Calling the SFTP engine's `new` with a local URL (Url::Os or Url::Unix without auth), e.g. routing every file through Vfs without checking scheme.
Common situations: Preview/thumbnail or task code that assumes Vfs handles all URLs but passes a purely local path; a missing `auth` component after hand-building a Unix URL; config pointing a remote path at a non-remote scheme.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
AI-assisted analysis of sxyazi/yazi@8ebf930f17 (2026-09-09).
Data as JSON: /api/errors/1b1882d027577058.
Report an issue: GitHub.