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
- Exclude setup configuration paths from encrypted sources (tighten globs or remove the explicit entry).
- Track configuration files as plain files in the setup store instead of encrypted ones.
- 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
- Enumerate encrypted sources explicitly rather than with catch-all globs.
- Run a dry-run listing of files to be encrypted and inspect for config paths.
- Keep setup configuration in a directory excluded from dotfile sync.
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
- setup configuration itself cannot be encrypted: {path}
- remote cache blob packs require a positive max_pack_bytes li
- no age recipients to encrypt for
- plaintext exceeds the size limit
- compressed payload exceeds the size limit
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/9c645d31e52026cb.
Report an issue: GitHub.