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
- 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().
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
- 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.
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
- journal source is not a regular file: {}
- cannot promote a compensating materialization
- materialization is not in a completable phase
- failed to allocate a unique runtime candidate after 16 attem
- runtime candidate directory is a symlink or reparse point: {
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/257434d2a899b745.
Report an issue: GitHub.