sxyazi/yazi · warning · io::Error

Remote file has changed since last download

Error message

Remote file has changed since last download

What it means

Returned by Stamp::validate with ErrorKind::InvalidData when the 26-byte signature (Cha hashed via hash_u128_str) stored in the stamp no longer equals the hash of the file's current metadata. This is the intended integrity signal: the remote file was modified after it was downloaded into the local cache, so the cached copy is stale.

Source

Thrown at yazi-vfs/src/stamp.rs:81

	fn encode(cha: Cha, url: Url) -> io::Result<Vec<u8>> {
		let name = url.name().ok_or_else(|| io::Error::other("URL has no filename"))?;

		let mut buf = Vec::with_capacity(Self::SIG_LEN + name.len());
		buf.extend_from_slice(cha.hash_u128_str(&mut [0; Self::SIG_LEN]).as_bytes());
		buf.extend_from_slice(name.encoded_bytes());

		Ok(buf)
	}

	pub fn validate(&self, cha: Cha, url: Url) -> io::Result<()> {
		let name = url.name().ok_or_else(|| io::Error::other("URL has no filename"))?;
		if self.name() != name.encoded_bytes() {
			return Err(io::Error::new(io::ErrorKind::InvalidData, "Cache stamp does not match target"));
		}

		if self.sig() != cha.hash_u128_str(&mut [0; Self::SIG_LEN]) {
			return Err(io::Error::new(
				io::ErrorKind::InvalidData,
				"Remote file has changed since last download",
			));
		}
		Ok(())
	}

	#[inline]
	pub fn sig(&self) -> &str { unsafe { str::from_utf8_unchecked(&self.0[..Self::SIG_LEN]) } }

	#[inline]
	pub fn name(&self) -> &[u8] { &self.0[Self::SIG_LEN..] }
}

impl TryFrom<Vec<u8>> for Stamp {
	type Error = io::Error;

	fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {

View on GitHub (pinned to 94abcfa92f)

Solutions

  1. Invalidate the cached copy and re-download the file — this error is designed to trigger exactly that
  2. Delete the cached entry and its stamp under the auth's cache root and %stamp root, then retry the operation
  3. If the change is only mtime noise (for example rsync -a touching files), ignore mtime in your own comparison or re-stamp after confirming content is unchanged
  4. In plugins and wrappers, match ErrorKind::InvalidData from validate() and trigger a fresh fetch instead of surfacing the error to the user

Example fix

// before
stamp.validate(cha, url.as_url())?; // "Remote file has changed since last download" surfaces to the user

// after
match stamp.validate(cha, url.as_url()) {
    Ok(()) => use_cached(url).await,
    Err(e) if e.kind() == io::ErrorKind::InvalidData => refetch(url).await,
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: fallback

Try / catch

match stamp.validate(cha, url.as_url()) {
    Ok(()) => use_cached(url).await,
    Err(e) if e.kind() == io::ErrorKind::InvalidData => refetch(url).await, // remote changed => refresh
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: The remote file behind an SFTP/Hub/Mount URL is edited, replaced, or touched (changing size/mtime captured in Cha) between the download that wrote the stamp and a later validate(cha, url) call; the cached copy then corresponds to an older version of the file.

Common situations: Previewing a remote log or document that is still being written; another client editing the file while yazi holds a cached copy; server-side rsync/touch changing mtime without content changes.

Related errors


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