jdx/mise · error · eyre::Report

invalid remote cache digest

Error message

invalid remote cache digest

What it means

validate() requires the hash field to be exactly 64 characters of lowercase hexadecimal (0-9, a-f). Both blake3's standard 32-byte output and sha256 produce exactly this form, so the error indicates uppercase hex, a wrong-length digest, or non-hex characters. The check runs on every operation because the hash is interpolated directly into remote URLs and filesystem paths.

Source

Thrown at crates/mise-cache-core/src/lib.rs:124

        let (hash, size) = hash_file_blake3(path)?;
        Ok(Self {
            algorithm: "blake3".into(),
            hash,
            size,
        })
    }

    pub fn validate(&self) -> Result<()> {
        if self.algorithm != "blake3" && self.algorithm != "sha256" {
            bail!("unsupported remote cache digest algorithm");
        }
        if self.hash.len() != 64
            || !self
                .hash
                .bytes()
                .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
        {
            bail!("invalid remote cache digest");
        }
        Ok(())
    }

    pub fn matches_bytes(&self, bytes: &[u8]) -> Result<bool> {
        self.validate()?;
        if self.size != bytes.len() as u64 {
            return Ok(false);
        }
        let hash = match self.algorithm.as_str() {
            "blake3" => blake3::hash(bytes).to_hex().to_string(),
            "sha256" => hex::encode(sha2::Sha256::digest(bytes)),
            _ => unreachable!("digest algorithm was validated"),
        };
        Ok(self.hash == hash)
    }

    pub fn matches_file(&self, path: &Path) -> Result<bool> {

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Lowercase the hash and strip any whitespace, quotes, or 0x prefix before constructing the digest
  2. Verify you used the standard 32-byte digest: blake3::Hasher::finalize() (not extended output) or Sha256::digest
  3. Recompute the digest from the actual content with CacheDigest::blake3()/blake3_file() instead of assembling it by hand
  4. Run digest.validate() immediately after deserializing digests from external JSON to catch format drift early

Example fix

// before
let digest = CacheDigest { algorithm: "blake3".into(), hash: hash.to_uppercase(), size };

// after
let digest = CacheDigest { algorithm: "blake3".into(), hash: hash.to_lowercase(), size };
Defensive patterns

Strategy: validation

Validate before calling

fn has_valid_hash_format(digest: &CacheDigest) -> bool {
    digest.hash.len() == 64
        && digest.hash.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
}

fn normalized_digest(digest: &CacheDigest) -> eyre::Result<CacheDigest> {
    let mut d = digest.clone();
    d.algorithm = d.algorithm.to_lowercase();
    d.hash = d.hash.trim().trim_start_matches("0x").to_lowercase();
    d.validate()?;
    Ok(d)
}

Type guard

fn is_well_formed_digest(digest: &CacheDigest) -> bool {
    matches!(digest.algorithm.as_str(), "blake3" | "sha256")
        && digest.hash.len() == 64
        && digest.hash.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
}

Prevention

When it happens

Trigger: A digest whose hash is uppercase hex; a 40-character sha1 hash; a 128-character blake3 extended (XOF) output; a hash with a "0x" prefix, whitespace, or newline; an empty string. Thrown from validate() reached via matches_bytes, matches_file, all RemoteCacheClient endpoint builders, and LocalCas/LocalActionCache path_for/find/store.

Common situations: Copying hashes from tools that render hex uppercase; using blake3 extend_output instead of finalize; hand-editing manifest JSON; test fixtures with placeholder hashes like "deadbeef"; a database column or template that trims or re-encodes the hash.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/bb096f77fc103092. Report an issue: GitHub.