{"record":{"id":"9ebf79447c1aacad","repo":"libnyanpasu/clash-nyanpasu","slug":"cannot-complete-materialization-with-a-target-hash","errorCode":null,"errorMessage":"cannot complete materialization with a target hash mismatch","messagePattern":"cannot complete materialization with a target hash mismatch","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":1667,"sourceCode":"                &root,\n                operation_id,\n                JournalLocation::FilePromoting,\n                JournalLocation::FilePromoted,\n            )?;\n        }\n        Ok(())\n    }\n\n    fn complete(&self, prepared: &PreparedMaterialization) -> anyhow::Result<()> {\n        let root = self.ensure_materialization_layout()?;\n        let operation_id = prepared.operation_id();\n        let Some((mut location, journal)) = self.locate_materialization(&root, operation_id)?\n        else {\n            return Ok(());\n        };\n        let target = self.resolve(&journal.managed_path)?;\n        if Self::path_hash(&target)? != journal.hash {\n            bail!(\"cannot complete materialization with a target hash mismatch\");\n        }\n        if location == JournalLocation::FilePromoting {\n            Self::transition_journal(\n                &root,\n                operation_id,\n                JournalLocation::FilePromoting,\n                JournalLocation::FilePromoted,\n            )?;\n            location = JournalLocation::FilePromoted;\n        }\n        if !matches!(\n            location,\n            JournalLocation::StatePromoting | JournalLocation::FilePromoted\n        ) {\n            bail!(\"materialization is not in a completable phase\");\n        }\n        Self::remove_operation_artifacts(\n            &root,","sourceCodeStart":1649,"sourceCodeEnd":1685,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L1649-L1685","documentation":"The complete() step detected that the hash of the file at the journal's managed_path no longer matches the hash recorded in the materialization journal. Complete is the final fencing check that the promoted content is exactly what was prepared; any divergence means something else modified the target after promote, and committing cleanup would silently accept corrupted or overwritten data.","triggerScenarios":"Calling complete(prepared) when Self::path_hash(target) != journal.hash: the managed file was edited (by the user, an editor, or another service) between promote and complete; promote's promote_resource silently failed to write the expected bytes; a different profile write raced and replaced the file; the resolve() target differs from what prepare hashed.","commonSituations":"User saves the profile file in an external editor while an update/apply pipeline is mid-flight; two concurrent profile operations targeting the same path; antivirus or sync tools rewriting the file; a symlink target changed so resolve() now reads different content.","solutions":["Re-run the whole sequence: compensate() (or remove the stale journal), then prepare -> promote -> complete with the new content, so the journal hash matches reality.","Investigate what modified the managed path between promote and complete and lock/serialize writes to that path.","Do NOT call complete() after mutating the target yourself; the fence is intentional — restart the materialization instead.","Ensure only one pipeline writes a given managed profile path at a time (actor/queue per path)."],"exampleFix":"// before\nservice.write_external_edit(&path, edited)?; // target diverged\nservice.complete(&prepared)?; // hash mismatch -> bail\n\n// after\nservice.compensate(&prepared)?; // or remove stale journal\nlet prepared = service.prepare_state_first(&path, resource, new_revision)?;\nservice.promote(&prepared)?;\nservice.complete(&prepared)?;","handlingStrategy":"try-catch","validationCode":"let target = service.resolve(&journal_managed_path)?;\nlet current = hash_file(&target)?;\nlet expected = /* hash recorded in the journal */;\nanyhow::ensure!(current == expected, \"target {} diverged before complete; restart materialization\", target.display());","typeGuard":null,"tryCatchPattern":"match service.complete(&prepared) {\n    Err(e) if e.to_string().contains(\"hash mismatch\") => {\n        // target was modified externally; restart the operation with fresh content\n        let prepared = service.prepare_state_first(&path, resource, new_revision)?;\n        service.promote(&prepared)?;\n        service.complete(&prepared)?;\n    }\n    other => other?,\n}","preventionTips":["Do not edit managed profile files while an apply/update pipeline is in flight.","Serialize all writes to a given managed path through one owner (actor/queue).","Exclude app data directories from antivirus/sync tools that rewrite files in place.","Between promote and complete, avoid operations that change the managed path's resolved target."],"tags":["integrity","hash-mismatch","two-phase-commit","concurrency"],"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"}