libnyanpasu/clash-nyanpasu · error

materialization journal not found for operation {operation_i

Error message

materialization journal not found for operation {operation_id}

What it means

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.

Source

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

        &self,
        path: &ManagedProfilePath,
        resource: MaterializationResource,
        expected_revision: u64,
    ) -> anyhow::Result<PreparedMaterialization> {
        self.prepare_materialization(
            path,
            &resource,
            expected_revision,
            JournalLocation::FilePrepared,
        )
    }

    fn promote(&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 {
            bail!("materialization journal not found for operation {operation_id}");
        };

        if let Some(promoting) = location.promoting() {
            Self::transition_journal(&root, operation_id, location, promoting)?;
            location = promoting;
        }
        if matches!(
            location,
            JournalLocation::StateCompensating | JournalLocation::FileCompensating
        ) {
            bail!("cannot promote a compensating materialization");
        }

        let target = self.resolve(&journal.managed_path)?;
        if Self::path_hash(&target)? != journal.hash {
            self.promote_resource(&root, operation_id, &target, &journal.hash)?;
        }
        if location == JournalLocation::FilePromoting {

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Ensure promote() is called exactly once per prepared operation and is not retried after a successful complete()/compensate().
  2. Verify the materialization root directory still exists and was not cleaned between prepare and promote; re-run prepare if it was wiped.
  3. Use the same ProfileFileService instance/root that produced the PreparedMaterialization handle.
  4. Treat this as an idempotent no-op-or-error: if the operation already finished, skip promote and continue with complete().

Example fix

// before
service.promote(&prepared)?; // second call after complete -> journal gone
service.complete(&prepared)?;

// after
if !promoted_operations.contains(prepared.operation_id()) {
    service.promote(&prepared)?;
}
service.complete(&prepared)?;
Defensive patterns

Strategy: try-catch

Validate before calling

fn journal_exists(root: &std::path::Path, op_id: &str) -> bool {
    std::fs::read_dir(root.join("materialization"))
        .map(|entries| entries.filter_map(|e| e.ok()).any(|e| e.file_name().to_string_lossy().contains(op_id)))
        .unwrap_or(false)
}

Try / catch

match service.promote(&prepared) {
    Err(e) if e.to_string().contains("journal not found") => {
        // operation already finished or root wiped; re-prepare instead of promoting
        let prepared = service.prepare_state_first(&path, resource, revision)?;
        service.promote(&prepared)?;
    }
    other => other?,
}

Prevention

When it happens

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

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

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/257434d2a899b745. Report an issue: GitHub.