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
- Remove the control/setup configuration file from the set of paths being encrypted.
- Narrow the encryption path list/glob so it only includes external dotfiles, not the sync configuration.
- 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
- Exclude the sync configuration path from every encryption glob.
- Use negation patterns for control files in include lists.
- Keep sync config in a directory outside the encrypted source root.
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
- encrypt an external dotfile source instead of configuration
- encrypted file does not match its path or mode
- encrypted file exceeds the size limit
- [history.encryption].recipients must not be empty…
- relay log format must be text or jsonl
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)