{"record":{"id":"257434d2a899b745","repo":"libnyanpasu/clash-nyanpasu","slug":"materialization-journal-not-found-for-operation-o","errorCode":null,"errorMessage":"materialization journal not found for operation {operation_id}","messagePattern":"materialization journal not found for operation (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":1629,"sourceCode":"        &self,\n        path: &ManagedProfilePath,\n        resource: MaterializationResource,\n        expected_revision: u64,\n    ) -> anyhow::Result<PreparedMaterialization> {\n        self.prepare_materialization(\n            path,\n            &resource,\n            expected_revision,\n            JournalLocation::FilePrepared,\n        )\n    }\n\n    fn promote(&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            bail!(\"materialization journal not found for operation {operation_id}\");\n        };\n\n        if let Some(promoting) = location.promoting() {\n            Self::transition_journal(&root, operation_id, location, promoting)?;\n            location = promoting;\n        }\n        if matches!(\n            location,\n            JournalLocation::StateCompensating | JournalLocation::FileCompensating\n        ) {\n            bail!(\"cannot promote a compensating materialization\");\n        }\n\n        let target = self.resolve(&journal.managed_path)?;\n        if Self::path_hash(&target)? != journal.hash {\n            self.promote_resource(&root, operation_id, &target, &journal.hash)?;\n        }\n        if location == JournalLocation::FilePromoting {","sourceCodeStart":1611,"sourceCodeEnd":1647,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L1611-L1647","documentation":"The promote step of the two-phase materialization protocol cannot find a journal file for the given operation_id under the materialization root. Promote requires that prepare_state_first/prepare_file_first previously wrote a journal tracking the operation; without it the operation cannot be advanced and the service bails instead of guessing.","triggerScenarios":"Calling ProfileMaterializationPort::promote(&prepared) when locate_materialization() returns None: the journal was already consumed (promoted+completed or compensated earlier), the materialization root directory was wiped or recreated, the PreparedMaterialization handle comes from a different/rooted-at-a-different-path service instance, or prepare crashed before writing the journal.","commonSituations":"Double-promoting the same PreparedMaterialization after an earlier promote+complete; clearing the app's temp/data directory between prepare and promote; reusing a prepared handle persisted across an app restart after cleanup ran; passing a handle from a stale service instance whose layout root differs.","solutions":["Ensure promote() is called exactly once per prepared operation and is not retried after a successful complete()/compensate().","Verify the materialization root directory still exists and was not cleaned between prepare and promote; re-run prepare if it was wiped.","Use the same ProfileFileService instance/root that produced the PreparedMaterialization handle.","Treat this as an idempotent no-op-or-error: if the operation already finished, skip promote and continue with complete()."],"exampleFix":"// before\nservice.promote(&prepared)?; // second call after complete -> journal gone\nservice.complete(&prepared)?;\n\n// after\nif !promoted_operations.contains(prepared.operation_id()) {\n    service.promote(&prepared)?;\n}\nservice.complete(&prepared)?;","handlingStrategy":"try-catch","validationCode":"fn journal_exists(root: &std::path::Path, op_id: &str) -> bool {\n    std::fs::read_dir(root.join(\"materialization\"))\n        .map(|entries| entries.filter_map(|e| e.ok()).any(|e| e.file_name().to_string_lossy().contains(op_id)))\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match service.promote(&prepared) {\n    Err(e) if e.to_string().contains(\"journal not found\") => {\n        // operation already finished or root wiped; re-prepare instead of promoting\n        let prepared = service.prepare_state_first(&path, resource, revision)?;\n        service.promote(&prepared)?;\n    }\n    other => other?,\n}","preventionTips":["Promote each PreparedMaterialization exactly once; track completed operation ids.","Never wipe or recreate the materialization root between prepare and promote.","Keep the PreparedMaterialization handle and the service instance that created it together.","On startup recovery, reconcile journals before issuing new promote calls."],"tags":["filesystem","journal","two-phase-commit","materialization"],"backgroundTag":"record-not-found","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"}