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
- 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`.
- If a truncated/partial blob exists from a partial write, delete the whole pending record rather than trusting it.
- 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
- Do not prune or clean the mise state directory while operations are pending
- Back up the blobs directory alongside journals
- Complete or discard interrupted operations promptly so cleanup ordering stays valid
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
- changed while preparing recovery; left untouched
- a parent directory is now a symlink; left untouched
- {} changed during recovery; left untouched
- rustc output is not a regular file: {}
- brew-cask: unresolved recovery for {} still preserves its or
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/d903d83bc1e93409.
Report an issue: GitHub.