jdx/mise · error

incoming application requires an active recovery operation

Error message

incoming application requires an active recovery operation

What it means

validate_starting_head verifies that incoming history is applied against an active recovery operation writer. If the internal shared recovery state (self.0) is None, there is no active recovery operation to record the application against, so applying incoming changes is refused.

Source

Thrown at src/system/history/scope.rs:234

                .collect();
            writer.pending.checkpoint.labels = label.into_iter().map(str::to_owned).collect();
            writer.operation_mut().message = label.map(str::to_owned);
            if let Err(err) = writer.write_pending() {
                warn!("history: could not persist the capture label: {err:#}");
            }
        }
    }

    /// The protective checkpoint this operation took, if any.
    pub(crate) fn before(&self) -> Option<(u64, String)> {
        self.0
            .as_ref()
            .and_then(|shared| lock_unpoisoned(shared).before.clone())
    }

    pub(crate) fn validate_starting_head(&self, expected: Option<&str>) -> Result<()> {
        let Some(shared) = &self.0 else {
            eyre::bail!("incoming application requires an active recovery operation");
        };
        let writer = lock_unpoisoned(shared);
        if writer.starting_head.as_deref() != expected {
            eyre::bail!(
                "local saved history changed since planning; nothing was applied; run pull again"
            );
        }
        Ok(())
    }

    /// Retakes the protective checkpoint, replacing the earlier one, when
    /// files changed between it and the verified plan.
    pub(crate) fn recapture_before(&self, paths: &[PathBuf]) -> Result<()> {
        let Some(shared) = &self.0 else {
            return Ok(());
        };
        let mut writer = lock_unpoisoned(shared);
        // held across the reservation, the capture, and the removal: the

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Start the recovery/apply operation through the normal API so the shared writer is initialized before applying incoming history
  2. Re-run the full pull so planning and application happen in one operation
  3. Check for earlier errors that aborted operation setup and fix the root cause
Defensive patterns

Strategy: validation

Validate before calling

if recovery_writer.is_none() {
    return Err("start a recovery operation before applying incoming history".into());
}

Try / catch

match apply_incoming() {
    Err(e) if e.to_string().contains("active recovery operation") => start_recovery_then_apply(),
    other => other,
}

Prevention

When it happens

Trigger: Calling validate_starting_head (incoming history application) on a scope whose recovery writer has not been initialized — e.g. applying a pull plan without first starting/locking a recovery operation.

Common situations: Programmatic or partial use of the history apply path that skipped operation setup; a previous error dropped the recovery writer before apply; state dir missing the expected active operation.

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 jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/dab839af21281e5f. Report an issue: GitHub.