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 when the stored signature (self.sig()) differs from the current Cha's hash_u128_str. The cached data was valid for the target name, but the remote file's metadata/content signature has changed since the stamp was written, so the cache is stale.

Solutions

  1. Re-download the remote file and rewrite the stamp via Stamp::write with the fresh Cha
  2. Re-fetch current Cha metadata for the URL before validating to avoid false staleness
  3. If staleness is acceptable, bypass validation and serve cache with a warning

Example fix

// before
stamp.validate(cha, url)?;
// after
match stamp.validate(cha, url) {
    Ok(()) => use_cached(),
    Err(e) if e.to_string().contains("changed since last download") => redownload_and_restamp(url).await?,
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: validation

Validate before calling

let mut sig = [0u8; 16];
let current = cha.hash_u128_str(&mut sig);
if stamp.sig() != current { /* stale: re-download before serving cache */ }

Type guard

fn stamp_fresh(stamp: &Stamp, cha: &Cha) -> bool {
    let mut buf = [0u8; 16];
    stamp.sig() == cha.hash_u128_str(&mut buf)
}

Try / catch

match stamp.validate(cha, url) {
    Ok(()) => serve_cached(),
    Err(e) if e.to_string().contains("changed since last download") => redownload().await?,
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling stamp.validate(cha, url) after the remote file was modified, re-uploaded, or its metadata (size/mtime feeding the Cha hash) changed; also when validating with a Cha fetched at a different time than the stamp was written.

Common situations: Remote file updated on the server between downloads; clock/metadata skew on the remote; using a cached stamp after re-listing the remote directory shows new metadata.

Related errors


AI-assisted analysis of sxyazi/yazi@8ebf930f17 (2026-09-02). Data as JSON: /api/errors/9d5c934e8d2c72ee. Report an issue: GitHub.

Appendix: 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]
	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 8ebf930f17)