jdx/mise · error

setup configuration itself cannot be encrypted

Error message

setup configuration itself cannot be encrypted: {path}

What it means

This error is thrown by `decrypt` in the history-sync files module when an attempt is made to decrypt a file that is itself part of the sync's setup configuration (control files, e.g. config/metadata tracked by the sync system). Encrypting or decrypting these control files would corrupt the synchronization machinery, so they are explicitly rejected. It is a policy guard, not a data corruption.

Solutions

  1. Remove the control/setup configuration file from the set of paths being encrypted.
  2. Narrow the encryption path list/glob so it only includes external dotfiles, not the sync configuration.
  3. Move the setup configuration outside the encrypted source directory if both must coexist.

Example fix

// before (sync config listing)
encrypt = ["~/.config/**"]
// after
encrypt = ["~/.config/nvim/**", "~/.config/git/**"] // exclude the sync control file itself
Defensive patterns

Strategy: validation

Validate before calling

// skip files that are sync control files
if (isControlFile(path)) continue; // or surface a clear message to the user
await sync.decrypt(path);

Type guard

function isEncryptable(path: string): boolean {
  return !isControlFile(path);
}

Try / catch

try {
  await sync.decrypt(path);
} catch (e) {
  if (isControlFile(path)) log.warn(`skipping control file: ${path}`);
  else throw e;
}

Prevention

When it happens

Trigger: Calling `decrypt` (via `commit_object`) on a path for which `control_file(path)` returns true — i.e. the user added the sync setup configuration file itself as an encrypted dotfile.

Common situations: A user adds their entire dotfiles directory to sync encryption, sweeping in the setup store's own config file (e.g. sync configuration inside ~/.config that is also the control file); overly broad glob patterns in encrypted-file lists.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: 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 dot 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());

View on GitHub (pinned to 533346cc37)