jdx/mise · error

encrypt an external dotfile source instead of configuration:

Error message

encrypt an external dotfile source instead of configuration: {path}

What it means

encode() refuses to encrypt a path that control_file() identifies as mise setup configuration. Same policy as the decrypt-side guard: only external dotfile sources may be encrypted, so the setup store remains bootstrappable. The error fires before any recipients/ciphertext work is done.

Source

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

        .map(|recipient| {
            agecrypt::parse_recipient_mode(recipient, interactive)?
                .ok_or_else(|| eyre::eyre!("invalid age recipient: {recipient}"))
        })
        .collect::<Result<Vec<_>>>()?;
    encrypt(repo, path, object, &scheme, &recipients)
}

/// Encrypt bytes before they enter Git. Callers may store only the returned
/// envelope, never the input or the decrypted payload in repository objects.
pub(crate) fn encode(
    path: &str,
    mode: &str,
    content: &[u8],
    scheme: &str,
    recipients: &[Box<dyn age::Recipient + Send>],
) -> Result<Vec<u8>> {
    if control_file(path) {
        bail!("encrypt an external dotfile source instead of configuration: {path}");
    }
    if !matches!(mode, "100644" | "100755" | "120000") {
        bail!("unsupported encrypted file mode: {path}");
    }
    let inner = Plaintext {
        path: path.into(),
        mode: mode.into(),
        scheme: scheme.into(),
        content: Bytes(content.to_vec()),
    };
    let bytes = rmp_serde::to_vec_named(&inner)?;
    let outer = Envelope {
        path: path.into(),
        mode: mode.into(),
        scheme: scheme.into(),
        ciphertext: Bytes(agecrypt::encrypt_bytes(&bytes, recipients)?),
    };
    let mut encoded = MAGIC.to_vec();

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Exclude setup configuration paths from encrypted sources (tighten globs or remove the explicit entry).
  2. Track configuration files as plain files in the setup store instead of encrypted ones.
  3. Split configuration out of the dotfile source directory being encrypted.

Example fix

# before
dotfiles.enc_globs = ["**/*"]
# after
dotfiles.enc_globs = [".bashrc", ".gitconfig"]  # exclude mise setup config
Defensive patterns

Strategy: validation

Validate before calling

let sources: Vec<_> = config.dotfiles.sources
    .into_iter()
    .filter(|p| !control_file(p))
    .collect();

Try / catch

match encrypt(path, mode, content, scheme, recipients) {
    Ok(bytes) => store(bytes),
    Err(e) if e.to_string().starts_with("encrypt an external dotfile source") => {
        eprintln!("skipping mise config file {path}");
        Ok(())
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Calling encrypt() (and the audit/test paths rechecking policy) with a path for which control_file(path) returns true — i.e. trying to encrypt the setup repository's own configuration.

Common situations: Over-broad include globs matching mise's config files; user explicitly listing the setup config as an encrypted dotfile source; tooling that walks the whole store and encrypts everything.

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/9c645d31e52026cb. Report an issue: GitHub.