{"record":{"id":"f14fdd8ba4b113b7","repo":"libnyanpasu/clash-nyanpasu","slug":"promoted-target-hash-mismatch","errorCode":null,"errorMessage":"promoted target hash mismatch","messagePattern":"promoted target hash mismatch","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":944,"sourceCode":"                })?;\n            }\n            StoredResource::Symlink {\n                target: link_target,\n            } => {\n                let ready = Self::create_ready_link(root, operation_id, &link_target)?;\n                self.ensure_managed_parent(target)?;\n                Self::ensure_replaceable_target(target)?;\n                replace_atomic(&ready, target).with_context(|| {\n                    format!(\n                        \"promote staged symlink {} -> {}\",\n                        ready.display(),\n                        target.display()\n                    )\n                })?;\n            }\n        }\n        if Self::path_hash(target)? != expected_hash {\n            bail!(\"promoted target hash mismatch\");\n        }\n        Ok(())\n    }\n\n    fn backup_hash(root: &Path, operation_id: &str) -> anyhow::Result<String> {\n        match Self::read_backup_resource(root, operation_id)? {\n            Some(StoredResource::File { path }) => {\n                let content = std::fs::read(&path)\n                    .with_context(|| format!(\"read backup file {}\", path.display()))?;\n                Ok(hash_tagged(b\"file\", &content))\n            }\n            Some(StoredResource::Symlink { target }) => {\n                Ok(hash_tagged(b\"symlink\", target.as_str().as_bytes()))\n            }\n            None => Ok(ABSENT_HASH.to_owned()),\n        }\n    }\n","sourceCodeStart":926,"sourceCodeEnd":962,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L926-L962","documentation":"promote_resource verifies the final target after replacement by hashing it; the hash differs from expected_hash. This is the last-line integrity check ensuring the promoted file content is exactly what was staged and journaled, so a mismatch aborts the operation as failed rather than reporting success with corrupted data.","triggerScenarios":"promote() completed its copy/rename steps but the resulting target hashes differently — e.g. concurrent writes to the target, a copy that read a source mutated mid-flight, filesystem corruption, or the staged resource itself was tampered with after its hash check.","commonSituations":"Another process (editor, sync client) wrote the target during promotion; staged source was modified between prepare and promote; disk/filesystem errors silently corrupting content; target on a network mount with unreliable semantics.","solutions":["Rerun the whole materialization with a fresh operation id so a clean staged copy replaces the corrupted target.","Restore the target from the operation's backup (read_backup_resource / backup restore path) and re-attempt promote.","Investigate what concurrently modified the target (sync tools, editors, antivirus) and exclude the profile directory.","Verify disk health if corruption repeats across operations."],"exampleFix":"// before\n// target modified by another process during promote -> hash mismatch\n// after\n// close/sync tools writing to profiles dir, then:\nclient.compensate(root, operation_id).await?;\nlet new_id = client.prepare_materialization(&managed_path, resource).await?;\nclient.promote(root, &new_id).await?;","handlingStrategy":"try-catch","validationCode":"let hash = hash_file(&target_path)?;\nif hash != expected_hash {\n    // target already diverged; restore from backup or re-materialize before promote\n    restore_from_backup(root, op_id, &target_path)?;\n}","typeGuard":"fn target_matches_expected(target: &Path, expected: &str) -> bool {\n    hash_file(target).map(|h| h == expected).unwrap_or(false)\n}","tryCatchPattern":"match client.promote(root, op_id).await {\n    Err(e) if e.to_string().contains(\"hash mismatch\") => {\n        // rollback to backup, then retry with a fresh operation\n        client.compensate(root, op_id).await?;\n        let fresh = client.prepare_materialization(&managed_path, resource).await?;\n        client.promote(root, &fresh).await\n    }\n    r => r,\n}","preventionTips":["Hold an exclusive lock or pause sync/edit tools while promoting profile files.","Never write to the managed target path from outside the materialization API.","Keep staging and targets on the same reliable local filesystem.","Run reconcile on startup and surface hash mismatches to the user rather than ignoring them."],"tags":["filesystem","integrity","hash","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"}