{"record":{"id":"26ec2f9a6d91b010","repo":"libnyanpasu/clash-nyanpasu","slug":"materialization-journal-hash-is-invalid","errorCode":null,"errorMessage":"materialization journal hash is invalid","messagePattern":"materialization journal hash is invalid","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":633,"sourceCode":"            );\n        }\n        let content = std::fs::read_to_string(path)\n            .with_context(|| format!(\"read materialization journal {}\", path.display()))?;\n        let journal: MaterializationJournal = serde_yaml::from_str(&content)\n            .with_context(|| format!(\"parse materialization journal {}\", path.display()))?;\n        if journal.operation_id != operation_id || !valid_operation_id(&journal.operation_id) {\n            bail!(\"materialization journal operation id mismatch\");\n        }\n        if journal\n            .managed_path\n            .as_path()\n            .components()\n            .any(|component| is_materialization_root_name(component.as_os_str()))\n        {\n            bail!(\"materialization journal targets reserved private storage\");\n        }\n        if journal.hash.len() != 64 || !journal.hash.bytes().all(|byte| byte.is_ascii_hexdigit()) {\n            bail!(\"materialization journal hash is invalid\");\n        }\n        Ok(journal)\n    }\n\n    fn remove_nofollow(path: &Path) -> anyhow::Result<()> {\n        match std::fs::symlink_metadata(path) {\n            Ok(metadata) if metadata.is_dir() && !is_symlink_or_reparse(&metadata) => {\n                bail!(\n                    \"refusing to remove directory as a profile resource: {}\",\n                    path.display()\n                )\n            }\n            Ok(_) => {\n                std::fs::remove_file(path)\n                    .with_context(|| format!(\"remove profile resource {}\", path.display()))?;\n                sync_directory(path.parent().expect(\"profile resource has parent\"))\n            }\n            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),","sourceCodeStart":615,"sourceCodeEnd":651,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L615-L651","documentation":"The journal's `hash` field must be exactly 64 ASCII hex characters (a SHA-256 hex digest) recording the content hash of the managed target. `read_journal` rejects journals whose hash has the wrong length or non-hex characters, because a malformed hash cannot be compared against recomputed content hashes during recovery/verification.","triggerScenarios":"`read_journal` parses a journal where `hash` is empty, truncated, uppercase-with-symbols, base64, or otherwise not 64 chars of [0-9a-f]. Caused by hand-edited journals, corruption, or a journal written by an incompatible/older schema.","commonSituations":"Manually edited YAML; truncated file from a disk/sync issue; journal produced by a different tool version using a different hash encoding.","solutions":["Delete the corrupt journal so the operation is re-derived and a fresh journal with a valid hash is written","Re-materialize the profile; the hash is recomputed from actual content","If inspecting, verify the hash is 64 hex chars matching a SHA-256 of the staged content","Avoid editing journal YAML by hand"],"exampleFix":"// before (journal YAML)\n// hash: abc123\nhash: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\n// after: full 64-char lowercase hex SHA-256 digest","handlingStrategy":"validation","validationCode":"fn valid_sha256_hex(h: &str) -> bool {\n    h.len() == 64 && h.bytes().all(|b| b.is_ascii_hexdigit())\n}\n// check before writing or trusting a journal hash","typeGuard":"fn has_valid_hash(j: &MaterializationJournal) -> bool {\n    valid_sha256_hex(&j.hash)\n}","tryCatchPattern":"match read_journal(&path, &op_id) {\n    Err(e) if e.to_string().contains(\"hash is invalid\") => {\n        let _ = std::fs::remove_file(&path); // corrupt journal; regenerate via materialization\n    }\n    Err(e) => return Err(e),\n    Ok(j) => use_journal(j),\n}","preventionTips":["Always store hashes as 64-char lowercase hex SHA-256","Never truncate or re-encode hashes (e.g. base64) when editing storage","Detect disk/sync corruption early by validating journal fields after restore","Regenerate journals through the app instead of editing YAML"],"tags":["validation","integrity","yaml"],"backgroundTag":"checksum-mismatch","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}