{"record":{"id":"3dd6b6d049cff234","repo":"libnyanpasu/clash-nyanpasu","slug":"materialization-operation-has-mixed-transaction-fa","errorCode":null,"errorMessage":"materialization operation has mixed transaction families","messagePattern":"materialization operation has mixed transaction families","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":1154,"sourceCode":"                    return Err(error)\n                        .with_context(|| format!(\"inspect journal {}\", path.display()));\n                }\n            }\n        }\n        Self::converge_materialization_journals(found)\n    }\n\n    fn converge_materialization_journals(\n        mut found: Vec<(JournalLocation, MaterializationJournal, PathBuf)>,\n    ) -> anyhow::Result<Option<(JournalLocation, MaterializationJournal)>> {\n        let Some(family) = found.first().map(|(location, _, _)| location.family()) else {\n            return Ok(None);\n        };\n        if found\n            .iter()\n            .any(|(location, _, _)| location.family() != family)\n        {\n            bail!(\"materialization operation has mixed transaction families\");\n        }\n        found.sort_by_key(|(location, _, _)| location.rank());\n        let (location, journal, _) = found\n            .pop()\n            .expect(\"non-empty materialization journal set has a preferred phase\");\n        for (duplicate_location, duplicate_journal, duplicate_path) in found {\n            if duplicate_journal != journal {\n                bail!(\n                    \"materialization operation has conflicting journal payloads in {duplicate_location:?}\"\n                );\n            }\n            Self::remove_private_regular(&duplicate_path)?;\n        }\n        Ok(Some((location, journal)))\n    }\n\n    /// The journal locations share one private root, so Unix `rename` is an\n    /// atomic same-filesystem phase transition. Do not use `move_atomic`: its","sourceCodeStart":1136,"sourceCodeEnd":1172,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L1136-L1172","documentation":"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.","triggerScenarios":"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.","commonSituations":"Upgrading/downgrading the app while an operation was mid-flight; manually copying staging state between installations; restoring backups that mix journal layouts.","solutions":["Determine which family is current for this app version and remove the foreign-family journals for this operation id, then rerun reconcile.","Roll the operation back with a manual cleanup of all its journals/staged files and re-materialize with a new operation id.","Avoid restoring staging directories from backups made by different app versions; only restore profiles, not journals.","If this follows a version downgrade, upgrade back or clean the staging root before using the service."],"exampleFix":"// before\n// journals from two transaction families exist for one operation id\n// after\nfor path in foreign_family_journal_paths(root, operation_id) {\n    std::fs::remove_file(path)?; // keep only current family\n}\nclient.reconcile(root).await?;","handlingStrategy":"try-catch","validationCode":"// before resuming, ensure all journals for this op belong to one family\nlet families: HashSet<_> = existing_journal_families(root, op_id).into_iter().collect();\nif families.len() > 1 {\n    // remove journals of the obsolete family or compensate from scratch\n    remove_foreign_family_journals(root, op_id, current_family)?;\n}","typeGuard":null,"tryCatchPattern":"match client.reconcile(root).await {\n    Err(e) if e.to_string().contains(\"mixed transaction families\") => {\n        // unrecoverable automatically: purge this op and re-materialize\n        purge_operation(root, op_id)?;\n        let fresh = client.allocate_operation_id(root).await?;\n        /* start over */\n        bail!(\"operation reset due to mixed journal families\")\n    }\n    r => r,\n}","preventionTips":["Don't restore journal/staging directories from backups made by different app versions.","Complete or compensate in-flight operations before upgrading/downgrading the app.","Never copy operation ids across transaction types.","On version migration, migrate or clear the staging root explicitly."],"tags":["journal","transactions","corruption"],"backgroundTag":"invalid-state-transition","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"}