libnyanpasu/clash-nyanpasu · error

materialization operation has mixed transaction families

Error message

materialization operation has mixed transaction families

What it means

converge_materialization_journals found journals for the same operation id in multiple transaction-family locations, and not all of them belong to the same family. Each journal location belongs to a transaction family; mixing families means journals from different protocol generations or transaction types coexist for one id, which cannot be safely ordered, so it aborts.

Source

Thrown at backend/tauri/src/service/profile_file.rs:1154

                    return Err(error)
                        .with_context(|| format!("inspect journal {}", path.display()));
                }
            }
        }
        Self::converge_materialization_journals(found)
    }

    fn converge_materialization_journals(
        mut found: Vec<(JournalLocation, MaterializationJournal, PathBuf)>,
    ) -> anyhow::Result<Option<(JournalLocation, MaterializationJournal)>> {
        let Some(family) = found.first().map(|(location, _, _)| location.family()) else {
            return Ok(None);
        };
        if found
            .iter()
            .any(|(location, _, _)| location.family() != family)
        {
            bail!("materialization operation has mixed transaction families");
        }
        found.sort_by_key(|(location, _, _)| location.rank());
        let (location, journal, _) = found
            .pop()
            .expect("non-empty materialization journal set has a preferred phase");
        for (duplicate_location, duplicate_journal, duplicate_path) in found {
            if duplicate_journal != journal {
                bail!(
                    "materialization operation has conflicting journal payloads in {duplicate_location:?}"
                );
            }
            Self::remove_private_regular(&duplicate_path)?;
        }
        Ok(Some((location, journal)))
    }

    /// The journal locations share one private root, so Unix `rename` is an
    /// atomic same-filesystem phase transition. Do not use `move_atomic`: its

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Determine which family is current for this app version and remove the foreign-family journals for this operation id, then rerun reconcile.
  2. Roll the operation back with a manual cleanup of all its journals/staged files and re-materialize with a new operation id.
  3. Avoid restoring staging directories from backups made by different app versions; only restore profiles, not journals.
  4. If this follows a version downgrade, upgrade back or clean the staging root before using the service.

Example fix

// before
// journals from two transaction families exist for one operation id
// after
for path in foreign_family_journal_paths(root, operation_id) {
    std::fs::remove_file(path)?; // keep only current family
}
client.reconcile(root).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// before resuming, ensure all journals for this op belong to one family
let families: HashSet<_> = existing_journal_families(root, op_id).into_iter().collect();
if families.len() > 1 {
    // remove journals of the obsolete family or compensate from scratch
    remove_foreign_family_journals(root, op_id, current_family)?;
}

Try / catch

match client.reconcile(root).await {
    Err(e) if e.to_string().contains("mixed transaction families") => {
        // unrecoverable automatically: purge this op and re-materialize
        purge_operation(root, op_id)?;
        let fresh = client.allocate_operation_id(root).await?;
        /* start over */
        bail!("operation reset due to mixed journal families")
    }
    r => r,
}

Prevention

When it happens

Trigger: The same operation_id has journal files under locations of different JournalLocation::ALL families — e.g. journals copied between app versions with different layouts, or an operation id reused across transaction families.

Common situations: Upgrading/downgrading the app while an operation was mid-flight; manually copying staging state between installations; restoring backups that mix journal layouts.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/3dd6b6d049cff234. Report an issue: GitHub.