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 confirms an incoming (pulled) history application is tied to an active recovery operation holding a shared writer. If no recovery operation is active (self.0 is None), applying incoming data is an invalid state, so it bails.

Solutions

  1. Start the recovery/apply flow through the public API that takes the recovery lock first, then re-run pull/apply
  2. Re-run pull to plan and apply in one properly locked session
  3. Check that no earlier step aborted and cleared the recovery operation before apply

Example fix

// before: apply without active recovery
scope.validate_starting_head(Some(head))?;
// after: enter recovery first
let scope = HistoryScope::begin_recovery(&state_dir)?; // acquires lock
scope.validate_starting_head(Some(head))?;
Defensive patterns

Strategy: validation

Validate before calling

if !history.recovery_active() {
    history.begin_recovery()?; // acquire recovery lock before validating
}

Try / catch

match scope.validate_starting_head(head) {
    Err(e) if e.to_string().contains("active recovery operation") => {
        scope.begin_recovery()?;
        scope.validate_starting_head(head)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling validate_starting_head on a scope whose shared recovery state was never initialized (no active recovery operation) — e.g. applying a pull's incoming changes without first acquiring/taking the recovery lock, or after the recovery state was dropped/finished.

Common situations: Scripting history pull/apply calls out of order; a crash or earlier error cleared the recovery state between plan and apply; running the apply step in a fresh process that never opened the recovery 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@533346cc37 (2026-09-17). Data as JSON: /api/errors/dab839af21281e5f. Report an issue: GitHub.

Appendix: source

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

                .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 533346cc37)