jdx/mise · error

cannot save

Error message

cannot save: {reason}

What it means

Before performing a save, capture checks `store.unavailable()` and aborts if the history store cannot be used (e.g. the store directory is unreadable, corrupt, or in an incompatible state). The reason string from the store is embedded in the error.

Solutions

  1. Read the reason embedded in the error message for the specific cause
  2. Verify the history state directory exists and is writable
  3. Repair or reset the store per the reason (re-initialize, fix permissions, free disk space)

Example fix

// before
mise dot save   // cannot save: store directory not writable
// after
chmod u+w ~/.local/share/mise/dotfiles-history
mise dot save
Defensive patterns

Strategy: try-catch

Validate before calling

store_dir=~/.local/share/mise/dotfiles-history
[ -d "$store_dir" ] && [ -w "$store_dir" ] || echo "store dir missing or unwritable"

Try / catch

mise dot save update || {
  status=$?
  echo "save failed (store unavailable?) — see message for reason"
  exit $status
}

Prevention

When it happens

Trigger: Running `mise dot save` when the history store reports itself unavailable via Store::unavailable(), evaluated at the start of capture().

Common situations: Corrupt or version-mismatched history store directory; unwritable/read-only state dir; store locked by an incompatible writer; disk full.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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

Appendix: source

Thrown at src/cli/dotfiles/save.rs:127

        draft.description = self.description.clone();
        draft.description_source = Some(if trigger == Trigger::Agent {
            DescriptionSource::Agent
        } else {
            DescriptionSource::User
        });
        draft.task = self.task.clone();
        draft.labels = self.label.clone();
        tokio::task::spawn_blocking(move || self.save_checkpoint(&store, &tracked, draft)).await?
    }

    fn capture(
        &self,
        store: &Store,
        tracked: &crate::system::history::tracked::TrackedSet,
        draft: Draft,
    ) -> Result<()> {
        if let Some(reason) = store.unavailable() {
            bail!("cannot save: {reason}");
        }
        // a save is a write: it waits for no running operation, refuses to
        // interleave with one, and closes one that died
        let _operation = crate::system::history::scope::take_operation_lock(store, tracked)?;
        match store.attempt(tracked, draft)? {
            Outcome::Created(entry) => {
                info!(
                    "history: saved checkpoint {}: {}",
                    entry.id, entry.checkpoint.description
                );
                Ok(())
            }
            Outcome::Unchanged => {
                info!("history: nothing changed since the latest checkpoint");
                Ok(())
            }
            Outcome::Unavailable(reason) => bail!("cannot save: {reason}"),
        }

View on GitHub (pinned to 533346cc37)