libnyanpasu/clash-nyanpasu · error
cannot complete materialization with a target hash mismatch
Error message
cannot complete materialization with a target hash mismatch
What it means
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.
Source
Thrown at backend/tauri/src/service/profile_file.rs:1667
&root,
operation_id,
JournalLocation::FilePromoting,
JournalLocation::FilePromoted,
)?;
}
Ok(())
}
fn complete(&self, prepared: &PreparedMaterialization) -> anyhow::Result<()> {
let root = self.ensure_materialization_layout()?;
let operation_id = prepared.operation_id();
let Some((mut location, journal)) = self.locate_materialization(&root, operation_id)?
else {
return Ok(());
};
let target = self.resolve(&journal.managed_path)?;
if Self::path_hash(&target)? != journal.hash {
bail!("cannot complete materialization with a target hash mismatch");
}
if location == JournalLocation::FilePromoting {
Self::transition_journal(
&root,
operation_id,
JournalLocation::FilePromoting,
JournalLocation::FilePromoted,
)?;
location = JournalLocation::FilePromoted;
}
if !matches!(
location,
JournalLocation::StatePromoting | JournalLocation::FilePromoted
) {
bail!("materialization is not in a completable phase");
}
Self::remove_operation_artifacts(
&root,View on GitHub (pinned to f7dbce2997)
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).
Example fix
// before service.write_external_edit(&path, edited)?; // target diverged service.complete(&prepared)?; // hash mismatch -> bail // after service.compensate(&prepared)?; // or remove stale journal let prepared = service.prepare_state_first(&path, resource, new_revision)?; service.promote(&prepared)?; service.complete(&prepared)?;
Defensive patterns
Strategy: try-catch
Validate before calling
let target = service.resolve(&journal_managed_path)?;
let current = hash_file(&target)?;
let expected = /* hash recorded in the journal */;
anyhow::ensure!(current == expected, "target {} diverged before complete; restart materialization", target.display()); Try / catch
match service.complete(&prepared) {
Err(e) if e.to_string().contains("hash mismatch") => {
// target was modified externally; restart the operation with fresh content
let prepared = service.prepare_state_first(&path, resource, new_revision)?;
service.promote(&prepared)?;
service.complete(&prepared)?;
}
other => other?,
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- promoted target hash mismatch
- proxy actor timed out; an operation may still be running, do
- materialization journal operation id mismatch
- materialization journal hash is invalid
- staged file hash mismatch
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/9ebf79447c1aacad.
Report an issue: GitHub.