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

  1. Delete the corrupt metadata file (and its chunks) so the broken entry is skipped, then retry list_entries.
  2. Check file permissions and that the file contains valid recovery metadata JSON.
  3. 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

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


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)