jdx/mise · error

setup configuration itself cannot be encrypted: {path}

Error message

setup configuration itself cannot be encrypted: {path}

What it means

decrypt() refuses to decrypt any path that is itself a mise setup control/configuration file. Encryption is only meant for user dotfiles sourced from external locations; encrypting the setup's own configuration would create a circular dependency where mise cannot bootstrap itself. This guard fires before any decryption is attempted.

Source

Thrown at src/system/history/sync/files.rs:255

        || inner.scheme != outer.scheme
        || !layout::is_safe_branch_path(path)
        || !matches!(inner.mode.as_str(), "100644" | "100755" | "120000")
    {
        bail!("encrypted file does not match its path or mode: {path}");
    }
    Ok(())
}

pub(crate) fn decrypt(
    repo: &HistoryRepo,
    path: &str,
    object: &Object,
    interactive: bool,
) -> Result<Object> {
    let outer = envelope(repo, object, agecrypt::MAX_ENCRYPTED_BYTES)?
        .ok_or_else(|| eyre::eyre!("missing encrypted file envelope: {path}"))?;
    if control_file(path) {
        bail!("setup configuration itself cannot be encrypted: {path}");
    }
    if outer.path != path {
        bail!("encrypted file does not match its path: {path}");
    }
    if let Some(decrypted) = repo.decrypted_object(&object.1) {
        return Ok(decrypted);
    }
    let bytes = agecrypt::decrypt_sync(&outer.ciphertext.0, interactive)
        .wrap_err_with(|| format!("cannot unlock {path}; run mise bootstrap dotfiles pull interactively with a matching age identity"))?;
    let inner: Plaintext =
        rmp_serde::from_slice(&bytes).wrap_err("invalid encrypted file payload")?;
    validate(path, &outer, &inner)?;
    let oid = repo.transient_blob_id(&inner.content.0)?;
    let decrypted = (inner.mode, oid);
    repo.remember_decrypted(&object.1, decrypted.clone());
    Ok(decrypted)
}

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Remove the setup configuration path from the encrypted dotfile sources and keep it as plain tracked config.
  2. Encrypt only external dotfile sources; store configuration unencrypted in the setup repository.
  3. Rebuild the history store without the control file (fresh setup store) if it is already committed encrypted.

Example fix

# before
[[dotfiles]]
source = "~/.config/mise/config.toml"
encrypt = true
# after
[[dotfiles]]
source = "~/.config/mise/config.toml"
encrypt = false
Defensive patterns

Strategy: validation

Validate before calling

if control_file(path) {
    return Err(anyhow!("refusing to encrypt setup configuration: {path}"));
}

Try / catch

match decrypt(repo, &object, path, interactive) {
    Ok(obj) => obj,
    Err(e) if e.to_string().starts_with("setup configuration itself cannot be encrypted") => {
        eprintln!("{path} is mise config; track it unencrypted");
        read_plain(path)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling decrypt (via commit_object) with a path that control_file(path) classifies as setup configuration (e.g. mise's own config files inside the history store).

Common situations: A user added mise's setup config to the encrypted set in their dotfiles config; a glob/pattern over-broadly matches control files; migrating an old store where config files were wrongly encrypted.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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