libnyanpasu/clash-nyanpasu · error

materialization is not in a completable phase

Error message

materialization is not in a completable phase

What it means

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.

Source

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

        };
        let target = self.resolve(&journal.managed_path)?;
        if Self::path_hash(&target)? != journal.hash {
            bail!("cannot complete materialization with a target hash mismatch");
        }
        if location == JournalLocation::FilePromoting {
            Self::transition_journal(
                &root,
                operation_id,
                JournalLocation::FilePromoting,
                JournalLocation::FilePromoted,
            )?;
            location = JournalLocation::FilePromoted;
        }
        if !matches!(
            location,
            JournalLocation::StatePromoting | JournalLocation::FilePromoted
        ) {
            bail!("materialization is not in a completable phase");
        }
        Self::remove_operation_artifacts(
            &root,
            operation_id,
            &Self::journal_path(&root, location, operation_id),
        )
    }

    fn compensate(&self, prepared: &PreparedMaterialization) -> anyhow::Result<()> {
        let root = self.ensure_materialization_layout()?;
        let operation_id = prepared.operation_id();
        let Some((mut location, journal)) = self.locate_materialization(&root, operation_id)?
        else {
            return Ok(());
        };
        let compensating = location.compensating();
        if location != compensating {
            Self::transition_journal(&root, operation_id, location, compensating)?;

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Always call promote(prepared) before complete(prepared) so the journal reaches StatePromoting/FilePromoted.
  2. For a stale journal in a non-completable phase, run compensate() to roll it back, then re-prepare the operation.
  3. Verify the recovery/resume logic only drives journals from completable phases, routing others through the appropriate transition first.
  4. Inspect the journal files under the materialization root to confirm the actual phase before resuming an interrupted operation.

Example fix

// before
let prepared = service.prepare_state_first(&path, resource, rev)?;
service.complete(&prepared)?; // skipped promote -> not completable

// after
let prepared = service.prepare_state_first(&path, resource, rev)?;
service.promote(&prepared)?;
service.complete(&prepared)?;
Defensive patterns

Strategy: validation

Validate before calling

if let Ok(Some((location, _journal))) = service.locate_materialization(&root, prepared.operation_id()) {
    anyhow::ensure!(
        matches!(location, JournalLocation::StatePromoting | JournalLocation::FilePromoted),
        "operation {} in phase {:?} is not completable; promote first or compensate",
        prepared.operation_id(), location
    );
}

Type guard

fn is_completable(loc: &JournalLocation) -> bool {
    matches!(loc, JournalLocation::StatePromoting | JournalLocation::FilePromoted)
}

Try / catch

if let Err(e) = service.complete(&prepared) {
    if e.to_string().contains("not in a completable phase") {
        service.promote(&prepared)?; // advance the journal first
        service.complete(&prepared)?;
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

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/7d38ad31eea10815. Report an issue: GitHub.