jdx/mise · error
staged blob size does not match the declared CAS digest
Error message
staged blob size does not match the declared CAS digest
What it means
After staging a file into the CAS, the staged file's byte length must equal `digest.size`. This check primarily guards the pre-verified fast path (`store_verified_file`, used when this crate already verified the content earlier): if the source's length changed between that verification and the staging copy, publishing is refused rather than storing a truncated or extended blob under a wrong size. When full verification runs (`store_file`), the digest check already includes size and would fail first.
Solutions
- Only pass fully-written, quiesced files to the CAS; finalize outputs before caching them.
- Recompute the digest from the current file with `CacheDigest::blake3_file` and use the verifying `store_file` instead of a pre-verified path.
- Retry after the writer has finished.
Example fix
// before: digest verified earlier, file grew since - size check bails cas.store_verified_file(&digest, &path); // after: hash the current file and use the verifying store let digest = CacheDigest::blake3_file(&path)?; cas.store_file(&digest, &path)?;
Defensive patterns
Strategy: validation
Validate before calling
let len = std::fs::metadata(&source)?.len();
if len != digest.size {
// the file changed since the digest was verified; recompute it
let digest = CacheDigest::blake3_file(&source)?;
return cas.store_file(&digest, &source);
}
cas.store_verified_file(&digest, &source)?; Prevention
- Pass only quiesced, fully-written files to pre-verified store paths.
- Re-derive the digest with blake3_file when any doubt exists about file stability.
- Retry after concurrent writers (builds, loggers) have exited.
When it happens
Trigger: The source file is appended to or truncated between the crate's earlier verification pass and the staging copy on the verified fast path - e.g. a log or output file still being written when it reaches the CAS.
Common situations: Mutable build outputs passed to the verified store path; writers racing the cache layer; files on synchronizing network mounts changing length between passes.
Related errors
- staged blob does not match the declared CAS digest
- bytes do not match the declared CAS digest
- cannot publish an action result with a missing blob
- local CAS blob failed digest verification
- expected pre-planted symlink destination to be refused
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/591e5f7573c9c6a4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/mise-cache-core/src/local.rs:107
if let Some(existing) = self.find(digest)? {
return Ok(existing);
}
let parent = destination.parent().expect("CAS path has a parent");
fs::create_dir_all(parent)?;
let staging = tempfile::tempdir_in(parent)?;
let temporary = staging.path().join("blob");
reflink_copy::reflink_or_copy(source, &temporary)?;
let temporary = tempfile::TempPath::try_from_path(temporary)?;
make_owner_writable(&temporary)?;
fs::OpenOptions::new()
.write(true)
.open(&temporary)?
.sync_all()?;
if verify && !digest.matches_file(&temporary)? {
bail!("staged blob does not match the declared CAS digest");
}
if fs::metadata(&temporary)?.len() != digest.size {
bail!("staged blob size does not match the declared CAS digest");
}
match temporary.persist_noclobber(&destination) {
Ok(()) => Ok(destination),
Err(error) if error.error.kind() == std::io::ErrorKind::AlreadyExists => self
.find(digest)?
.ok_or_else(|| eyre::eyre!("concurrent CAS write did not publish a valid blob")),
Err(error) => Err(error.error.into()),
}
}
fn store_with(
&self,
digest: &CacheDigest,
write: impl FnOnce(&mut tempfile::NamedTempFile) -> Result<()>,
) -> Result<PathBuf> {
let destination = self.path_for(digest)?;
if let Some(existing) = self.find(digest)? {
return Ok(existing);View on GitHub (pinned to 6f52dcdf99)