sinelaw/fresh · warning
Metadata file exists but couldn't be read
Error message
Metadata file exists but couldn't be read
What it means
load_entry saw the recovery metadata file on disk (meta_path.exists() was true) but read_metadata returned None — the file could not be parsed/loaded despite existing. This flags a corrupt or empty metadata file so list_entries reports the problem instead of silently dropping the entry.
Solutions
- Delete the corrupt metadata file (and its chunks) so the broken entry is skipped, then retry list_entries.
- Check file permissions and that the file contains valid recovery metadata JSON.
- Make metadata writes atomic (write temp file + rename) to avoid future truncation.
Example fix
// before
let entries = recovery.list_entries()?;
// after
let entries = match recovery.list_entries() {
Ok(e) => e,
Err(e) if e.kind() == io::ErrorKind::NotFound => {
eprintln!("some recovery metadata unreadable; cleaning up");
Vec::new()
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: try-catch
Validate before calling
let meta_path = storage.metadata_path(&id); let valid = meta_path.exists() && fs::read_to_string(&meta_path).map(|s| !s.trim().is_empty()).unwrap_or(false);
Type guard
fn metadata_readable(storage: &RecoveryStorage, id: &str) -> bool { matches!(storage.read_metadata(id), Ok(Some(_))) } Try / catch
match recovery.list_entries() {
Ok(entries) => entries,
Err(e) if e.kind() == io::ErrorKind::NotFound => {
eprintln!("unreadable recovery metadata; purging corrupt entries");
Vec::new()
}
Err(e) => return Err(e.into()),
} Prevention
- Write metadata atomically (temp file + rename) to prevent truncated files
- Skip-and-purge entries whose metadata cannot be parsed instead of failing the whole listing
- Check permissions and disk-full conditions when recovery metadata was written
When it happens
Trigger: list_entries -> load_entry on an id whose metadata JSON file exists but is empty, truncated (e.g. crash mid-write), or unreadable due to permissions/encoding so read_metadata deserialization fails and yields None.
Common situations: Power loss during metadata write, disk-full leaving a zero-byte file, a text editor or sync tool corrupting the JSON, or wrong file permissions after copying recovery dirs between users.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- (serde_json serialization error)
- Chunk content not found
- Chunk file not found
- Chunked recovery data not found
- could not read script
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/e437376297b327dc.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-editor/src/services/recovery/storage.rs:552
/// Read recovery content
pub fn read_content(&self, id: &str) -> io::Result<Option<Vec<u8>>> {
let (_, content_path) = self.recovery_paths(id);
if !content_path.exists() {
return Ok(None);
}
Ok(Some(fs::read(&content_path)?))
}
/// Load a complete recovery entry
pub fn load_entry(&self, id: &str) -> io::Result<Option<RecoveryEntry>> {
let (meta_path, content_path) = self.recovery_paths(id);
if !meta_path.exists() {
return Ok(None);
}
let metadata = self.read_metadata(id)?.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"Metadata file exists but couldn't be read",
)
})?;
// Require at least one chunk file
let chunk_paths = self.list_chunk_paths(id)?;
if chunk_paths.is_empty() {
return Ok(None);
}
Ok(Some(RecoveryEntry {
id: id.to_string(),
metadata,
content_path,
metadata_path: meta_path,
}))
}View on GitHub (pinned to 67894ca546)