sxyazi/yazi · error · io::Error
Not a SFTP URL: {url:?}
Error message
Not a SFTP URL: {url:?} What it means
The SFTP engine's constructor requires a `Url::Sftp { loc, auth }` variant; any other URL is rejected with InvalidInput and a message naming the actual variant received. It's a routing/type error: the SFTP engine was asked to operate on a location that isn't owned by the SFTP backend.
Source
Thrown at yazi-vfs/src/engine/sftp/sftp.rs:152
}
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::Sftp { loc, auth } = url else {
return Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not a SFTP URL: {url:?}")));
};
let config = Vfs::service::<&ServiceSftp>(auth)?;
Ok(Self::Me { url, path: loc.as_inner(), config })
}
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())
}
async fn remove_dir(&self) -> io::Result<()> { Ok(self.op().await?.rmdir(self.path).await?) }View on GitHub (pinned to 5f901b886b)
Solutions
- Verify the URL kind before calling: it must be `Url::Sftp`; add an assertion/guard on `url.kind()`/variant
- Route the operation through the generic VFS dispatcher (`yazi-vfs`) so the correct engine is selected by URL kind instead of calling the SFTP engine directly
- Fix code that reconstructs the URL (e.g. joining with a local path) and preserve the original Sftp URL
- Confirm the remote entry was mounted/created via SFTP APIs, not copied from a local listing
Example fix
// before
let file = SftpFile::new(some_local_url)?; // panics into InvalidInput
// after
if let Url::Sftp { .. } = &url {
let file = SftpFile::new(url)?;
} else {
return engine::open(url).await; // generic dispatch
} Defensive patterns
Strategy: type-guard
Validate before calling
if url:kind() ~= 'sftp' then error('expected SFTP URL, got '..tostring(url:kind())) end Type guard
fn is_sftp_url(url: &Url) -> bool {
matches!(url, Url::Sftp { .. })
} Try / catch
match SftpFile::new(url).await {
Err(e) if e.to_string().starts_with("Not a SFTP URL") => {
return vfs::File::open(url).await; // generic dispatch by kind
}
r => r,
} Prevention
- Route remote operations through the generic VFS layer instead of calling the SFTP engine directly
- Preserve the original URL when joining/rewriting paths — don't rebuild a Url from a local Path
- Check url kind before engine-specific calls at API boundaries
- After upgrades, verify URL variant construction still matches the Sftp shape
When it happens
Trigger: Calling SFTP-backed file operations with a local, Mount/Hub/Scope (custom Lua VFS), Archive, or Search URL; or a URL that was re-parented/rewritten so it lost its Sftp variant.
Common situations: Plugin or config code constructing URLs manually instead of through the VFS, tab previews mixing local and remote entries, or upgrades that changed URL variant names leaving stale constructed URLs.
Related errors
AI-assisted analysis of sxyazi/yazi@5f901b886b (2026-09-02).
Data as JSON: /api/errors/8e5a99a46fdece85.
Report an issue: GitHub.