{"record":{"id":"e437376297b327dc","repo":"sinelaw/fresh","slug":"metadata-file-exists-but-couldn-t-be-read","errorCode":null,"errorMessage":"Metadata file exists but couldn't be read","messagePattern":"Metadata file exists but couldn't be read","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/fresh-editor/src/services/recovery/storage.rs","lineNumber":552,"sourceCode":"    /// Read recovery content\n    pub fn read_content(&self, id: &str) -> io::Result<Option<Vec<u8>>> {\n        let (_, content_path) = self.recovery_paths(id);\n        if !content_path.exists() {\n            return Ok(None);\n        }\n        Ok(Some(fs::read(&content_path)?))\n    }\n\n    /// Load a complete recovery entry\n    pub fn load_entry(&self, id: &str) -> io::Result<Option<RecoveryEntry>> {\n        let (meta_path, content_path) = self.recovery_paths(id);\n\n        if !meta_path.exists() {\n            return Ok(None);\n        }\n\n        let metadata = self.read_metadata(id)?.ok_or_else(|| {\n            io::Error::new(\n                io::ErrorKind::NotFound,\n                \"Metadata file exists but couldn't be read\",\n            )\n        })?;\n\n        // Require at least one chunk file\n        let chunk_paths = self.list_chunk_paths(id)?;\n        if chunk_paths.is_empty() {\n            return Ok(None);\n        }\n\n        Ok(Some(RecoveryEntry {\n            id: id.to_string(),\n            metadata,\n            content_path,\n            metadata_path: meta_path,\n        }))\n    }","sourceCodeStart":534,"sourceCodeEnd":570,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor/src/services/recovery/storage.rs#L534-L570","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet entries = recovery.list_entries()?;\n// after\nlet entries = match recovery.list_entries() {\n    Ok(e) => e,\n    Err(e) if e.kind() == io::ErrorKind::NotFound => {\n        eprintln!(\"some recovery metadata unreadable; cleaning up\");\n        Vec::new()\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"try-catch","validationCode":"let meta_path = storage.metadata_path(&id);\nlet valid = meta_path.exists() && fs::read_to_string(&meta_path).map(|s| !s.trim().is_empty()).unwrap_or(false);","typeGuard":"fn metadata_readable(storage: &RecoveryStorage, id: &str) -> bool { matches!(storage.read_metadata(id), Ok(Some(_))) }","tryCatchPattern":"match recovery.list_entries() {\n    Ok(entries) => entries,\n    Err(e) if e.kind() == io::ErrorKind::NotFound => {\n        eprintln!(\"unreadable recovery metadata; purging corrupt entries\");\n        Vec::new()\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["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"],"tags":["recovery","corrupt-metadata","io","serialization"],"backgroundTag":"file-read-failed","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}