jdx/mise · error

invalid recovery content file

Error message

invalid recovery content file

What it means

read_blob loads preimage content either from an inline base64 blob or from a file under the state directory's blobs dir. This bail fires for the file case when the on-disk blob is missing as a regular file (missing, symlink, directory) or its length does not match blob.size. Recovery refuses to restore content it cannot trust to be complete.

Source

Thrown at src/system/history/recovery.rs:137

}

fn validate_blob_id(hash: &str) -> Result<()> {
    if hash.len() != 64 || !hash.bytes().all(|b| b.is_ascii_hexdigit()) {
        bail!("invalid recovery content identifier");
    }
    Ok(())
}

pub(super) fn read_blob(state_dir: &Path, blob: &Blob) -> Result<Vec<u8>> {
    use base64::Engine;
    validate_blob_id(&blob.sha256)?;
    let bytes = match &blob.inline {
        Some(inline) => base64::engine::general_purpose::STANDARD.decode(inline)?,
        None => {
            let path = super::journal::blobs_dir_in(state_dir).join(&blob.sha256);
            let metadata = std::fs::symlink_metadata(&path)?;
            if !metadata.is_file() || metadata.len() != blob.size {
                bail!("invalid recovery content file");
            }
            std::fs::read(path)?
        }
    };
    if bytes.len() as u64 != blob.size || hex::encode(sha2::Sha256::digest(&bytes)) != blob.sha256 {
        bail!("recovery content failed verification");
    }
    Ok(bytes)
}

fn validate_snapshot(state_dir: &Path, snapshot: &PathSnapshot) -> Result<()> {
    match snapshot {
        PathSnapshot::File { content, .. } => {
            read_blob(state_dir, content)?;
        }
        PathSnapshot::Dir {
            files, links, dirs, ..
        } => {

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Check the blobs directory in the mise state dir; if the blob is simply gone, the preimage cannot be restored — accept current contents with `recover <operation> --keep-current`.
  2. If a truncated/partial blob exists from a partial write, delete the whole pending record rather than trusting it.
  3. Restore the blob file from a backup of the state directory, ensuring its byte length matches blob.size, then rerun recovery.

Example fix

// before
$ ls ~/.local/share/mise/history/blobs/
ca9781...  (truncated: 12 bytes, expected 4096)

// after (restore blob from backup, size matches)
$ cp backup/blobs/ca9781... ~/.local/share/mise/history/blobs/
$ mise bootstrap dotfiles recover
Defensive patterns

Strategy: fallback

Validate before calling

// verify blobs exist and sizes match before recovery
let blob_path = blobs_dir.join(&blob.sha256);
let meta = std::fs::symlink_metadata(&blob_path)?;
assert!(meta.is_file() && meta.len() == blob.size, "blob missing or wrong size");

Try / catch

match result { Err(e) if e.to_string().contains("invalid recovery content file") => /* fall back to --keep-current or restore blob from backup */, other => other? }

Prevention

When it happens

Trigger: read_blob -> symlink_metadata on `blobs_dir_in(state_dir)/<sha256>` shows the entry is not a plain file, or metadata.len() != blob.size; happens when validating a snapshot in recover_path or when a restore re-reads content.

Common situations: A blob file was deleted (cleanup ran too early, or the user pruned the state dir), a partial write left a truncated blob, or a sync/tool replaced the blob with something else.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/d903d83bc1e93409. Report an issue: GitHub.