{"record":{"id":"7d38ad31eea10815","repo":"libnyanpasu/clash-nyanpasu","slug":"materialization-is-not-in-a-completable-phase","errorCode":null,"errorMessage":"materialization is not in a completable phase","messagePattern":"materialization is not in a completable phase","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":1682,"sourceCode":"        };\n        let target = self.resolve(&journal.managed_path)?;\n        if Self::path_hash(&target)? != journal.hash {\n            bail!(\"cannot complete materialization with a target hash mismatch\");\n        }\n        if location == JournalLocation::FilePromoting {\n            Self::transition_journal(\n                &root,\n                operation_id,\n                JournalLocation::FilePromoting,\n                JournalLocation::FilePromoted,\n            )?;\n            location = JournalLocation::FilePromoted;\n        }\n        if !matches!(\n            location,\n            JournalLocation::StatePromoting | JournalLocation::FilePromoted\n        ) {\n            bail!(\"materialization is not in a completable phase\");\n        }\n        Self::remove_operation_artifacts(\n            &root,\n            operation_id,\n            &Self::journal_path(&root, location, operation_id),\n        )\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)?;","sourceCodeStart":1664,"sourceCodeEnd":1700,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L1664-L1700","documentation":"complete() was invoked while the journal is in a phase that cannot be finalized. Only StatePromoting and FilePromoted journals are completable; anything else (e.g. still prepared, or a phase that was never transitioned) is rejected to preserve the two-phase commit state machine. This indicates promote/transition steps were skipped or the journal is stale.","triggerScenarios":"Calling complete(prepared) without a prior promote() for a journal left in a prepared/pre-promoting phase that did not auto-transition (e.g. a FilePrepared/StatePrepared journal whose promoting() transition path was not exercised), or completing a journal whose phase enum falls outside {StatePromoting, FilePromoted} due to recovery from a partially written journal.","commonSituations":"Calling complete() directly after prepare() skipping promote(); crash-recovery resuming with a journal in an unexpected phase; calling complete() twice (second call: artifacts removed so locate returns None and it no-ops — but a leftover journal in another phase hits this bail); mixing file-first and state-first flows and completing at the wrong step.","solutions":["Always call promote(prepared) before complete(prepared) so the journal reaches StatePromoting/FilePromoted.","For a stale journal in a non-completable phase, run compensate() to roll it back, then re-prepare the operation.","Verify the recovery/resume logic only drives journals from completable phases, routing others through the appropriate transition first.","Inspect the journal files under the materialization root to confirm the actual phase before resuming an interrupted operation."],"exampleFix":"// before\nlet prepared = service.prepare_state_first(&path, resource, rev)?;\nservice.complete(&prepared)?; // skipped promote -> not completable\n\n// after\nlet prepared = service.prepare_state_first(&path, resource, rev)?;\nservice.promote(&prepared)?;\nservice.complete(&prepared)?;","handlingStrategy":"validation","validationCode":"if let Ok(Some((location, _journal))) = service.locate_materialization(&root, prepared.operation_id()) {\n    anyhow::ensure!(\n        matches!(location, JournalLocation::StatePromoting | JournalLocation::FilePromoted),\n        \"operation {} in phase {:?} is not completable; promote first or compensate\",\n        prepared.operation_id(), location\n    );\n}","typeGuard":"fn is_completable(loc: &JournalLocation) -> bool {\n    matches!(loc, JournalLocation::StatePromoting | JournalLocation::FilePromoted)\n}","tryCatchPattern":"if let Err(e) = service.complete(&prepared) {\n    if e.to_string().contains(\"not in a completable phase\") {\n        service.promote(&prepared)?; // advance the journal first\n        service.complete(&prepared)?;\n    } else { return Err(e); }\n}","preventionTips":["Enforce the full sequence prepare -> promote -> complete; never skip promote.","In recovery code, drive each journal phase through its designated transition before completing.","Check the journal phase before resuming an interrupted operation.","Call complete() only once per operation; rely on its idempotent no-op for missing journals."],"tags":["state-machine","two-phase-commit","invalid-state-transition","materialization"],"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"}