{"record":{"id":"05feddf21cc7cb9a","repo":"libnyanpasu/clash-nyanpasu","slug":"compensation-fenced-by-diverged-target-at","errorCode":null,"errorMessage":"compensation fenced by diverged target at {}","messagePattern":"compensation fenced by diverged target at (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":1706,"sourceCode":"        )\n    }\n\n    fn compensate(&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            return Ok(());\n        };\n        let compensating = location.compensating();\n        if location != compensating {\n            Self::transition_journal(&root, operation_id, location, compensating)?;\n            location = compensating;\n        }\n\n        let target = self.resolve(&journal.managed_path)?;\n        if !self.restore_backup(&root, operation_id, &target, &journal.hash)? {\n            bail!(\n                \"compensation fenced by diverged target at {}\",\n                journal.managed_path\n            );\n        }\n        Self::remove_operation_artifacts(\n            &root,\n            operation_id,\n            &Self::journal_path(&root, location, operation_id),\n        )\n    }\n\n    fn prepare_cleanup(\n        &self,\n        path: &ManagedProfilePath,\n        expected_revision: u64,\n    ) -> anyhow::Result<PreparedCleanup> {\n        let root = self.ensure_materialization_layout()?;\n        let target = self.resolve(path)?;","sourceCodeStart":1688,"sourceCodeEnd":1724,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L1688-L1724","documentation":"During journal-based compensation of an aborted profile-file operation, the transaction framework attempts to restore the managed file from its backup. Before overwriting, it verifies the current content hash of the managed path against the hash recorded in the journal. If the file has changed since the transaction started ('diverged'), the framework refuses to blindly roll back and bails with this error, because restoring the backup would clobber changes the user or another process made after the transaction began.","triggerScenarios":"Calling the compensation/rollback path (e.g. after a failed materialization or explicit cancel) when the file at journal.managed_path was modified after the journal recorded its hash. Specifically, restore_backup(&root, operation_id, &target, &journal.hash) returns false because the on-disk content of the target no longer matches journal.hash.","commonSituations":"The user (or an external editor/sync tool) edited the profile YAML while a long-running operation (download, import, patch) was in flight and later aborted; a concurrent transaction wrote to the same managed path; clock/sync tools like cloud drive clients rewrote the file; retrying compensation after a partial previous restore.","solutions":["Let the user resolve the divergence manually: inspect journal.managed_path, keep or merge the new content, then clear/remove the operation journal artifacts so compensation is no longer fenced","Re-run compensation only after confirming the target content is back to the journaled state (e.g. revert the external edit or restore from the newer source deliberately)","Investigate the concurrent writer: ensure only one operation mutates the managed path at a time (operation-id fencing, file locking) and retry the operation in a fresh transaction","If intentionally discarding local changes, remove the journal/backup artifacts for the operation so the stale compensation is skipped instead of failing"],"exampleFix":"// before: blind compensation fails on diverged target\ncompensate(operation_id)?;\n// after: check divergence and ask user / drop the journal\nif !journal_target_matches(operation_id)? {\n    prompt_user_to_resolve(journal.managed_path)?;\n    discard_operation_journal(operation_id)?;\n} else {\n    compensate(operation_id)?;\n}","handlingStrategy":"validation","validationCode":"fn target_matches_journal(journal: &Journal, root: &Path) -> anyhow::Result<bool> {\n    let current = file_sha256(&journal.managed_path)?;\n    Ok(current == journal.hash)\n}\n// call compensate only if target_matches_journal(...)? else resolve manually","typeGuard":null,"tryCatchPattern":"match compensate(op_id) {\n    Err(e) if e.to_string().contains(\"fenced by diverged target\") => {\n        // surface conflict to user; do NOT retry blindly\n        prompt_manual_resolution(journal.managed_path)?;\n    }\n    other => other?,\n}","preventionTips":["Serialize all mutations of the managed path through one operation/journal owner","Check the journaled hash before starting any external edit or sync of the managed file","Avoid running cloud-sync/editors on managed paths while transactions are in flight","After a failed transaction, resolve compensation promptly before the target changes again"],"tags":["transaction","conflict","file","rollback"],"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"}