jdx/mise · error

another history operation is running

Error message

another history operation is running

What it means

Same lock-acquisition path as the parameterized variant: when the deadline expires and store::read_marker_in finds no marker, acquire_operation_lock bails with the generic 'another history operation is running'. The lockfile is held but no marker was recorded.

Solutions

  1. Confirm no mise history process is alive (ps), then delete the stale lockfile in MISE_STATE_DIR and retry
  2. Re-run the operation after ensuring only one mise instance runs
  3. If crashes recur, check for SIGKILL/OOM during history operations

Example fix

// before: stale lock
ps aux | grep mise   # confirm nothing running
rm "$(mise state-dir)/history.lock"   // after: remove stale lock, retry
Defensive patterns

Strategy: retry

Validate before calling

if history.operation_lock_held() && !history.operation_marker_exists() {
    eprintln!("stale lock without marker; safe to clear after confirming no mise process");
}

Try / catch

match history.take_operation_lock() {
    Err(e) if e.to_string() == "another history operation is running" => {
        if !history.any_mise_process_alive() {
            history.clear_stale_lock()?;
            history.take_operation_lock()?;
        }
    }
    other => other?,
}

Prevention

When it happens

Trigger: take_operation_lock_with_wait / recovery_lock timed out on a held LockFile::at(path) while no marker exists in the state dir — e.g. the holder crashed after locking but before writing its marker, or the marker file was deleted.

Common situations: Stale lockfile from a killed mise process; a holder that locked but died before writing the marker; racing processes on a shared MISE_STATE_DIR.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/b61407e313a05f5d. Report an issue: GitHub.

Appendix: source

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

    let path = store::operation_lock_in(state_dir);
    // a running operation (the watcher applying incoming changes, say) is
    // usually over in moments: wait for it a bounded while before failing
    let deadline = std::time::Instant::now() + wait;
    let mut announced = false;
    let lock = loop {
        if let Some(lock) = LockFile::at(&path).try_lock()? {
            break lock;
        }
        let marker = store::read_marker_in(state_dir)?;
        if std::time::Instant::now() >= deadline {
            match marker {
                Some(marker) => bail!(
                    "another history operation is running: {} since {} ({})",
                    marker.kind.as_str(),
                    marker.started_at,
                    marker.command
                ),
                None => bail!("another history operation is running"),
            }
        }
        if !announced {
            announced = true;
            info!(
                "history: waiting for another history operation to finish{}",
                marker
                    .map(|marker| format!(": {} ({})", marker.kind.as_str(), marker.command))
                    .unwrap_or_default()
            );
        }
        std::thread::sleep(OPERATION_LOCK_POLL);
    };
    Ok(lock)
}

/// How long an operation waits for a running one, and how often it looks.
const OPERATION_LOCK_WAIT: std::time::Duration = std::time::Duration::from_secs(30);

View on GitHub (pinned to 533346cc37)