jdx/mise · error
no age recipients to encrypt for
Error message
no age recipients to encrypt for
What it means
encrypt_bytes compresses plaintext and encrypts it for a supplied list of age recipients. An empty recipient list would produce an envelope nobody can decrypt, so the function rejects it up front with this error.
Source
Thrown at src/agecrypt.rs:154
pub(crate) fn read_bounded(reader: impl Read, limit: u64) -> std::io::Result<Vec<u8>> {
let mut bytes = Vec::new();
reader.take(limit + 1).read_to_end(&mut bytes)?;
if bytes.len() as u64 > limit {
return Err(std::io::Error::other(
"encrypted content exceeds the size limit",
));
}
Ok(bytes)
}
/// zstd-compressed, then age-encrypted for `recipients`.
pub(crate) fn encrypt_bytes(
plaintext: &[u8],
recipients: &[Box<dyn Recipient + Send>],
) -> Result<Vec<u8>> {
if recipients.is_empty() {
bail!("no age recipients to encrypt for");
}
if plaintext.len() as u64 > MAX_PLAINTEXT_BYTES {
bail!("plaintext exceeds the size limit");
}
let compressed = zstd::encode_all(plaintext, ZSTD_COMPRESSION_LEVEL)?;
if compressed.len() as u64 > MAX_ENCRYPTED_BYTES {
bail!("compressed payload exceeds the size limit");
}
let encryptor =
Encryptor::with_recipients(recipients.iter().map(|r| r.as_ref() as &dyn Recipient))
.map_err(|e| eyre!("creating the age encryptor: {e}"))?;
let mut out = Vec::new();
let mut writer = encryptor.wrap_output(&mut out)?;
writer.write_all(&compressed)?;
writer.finish()?;
if out.len() as u64 > MAX_ENCRYPTED_BYTES {
bail!("encrypted payload exceeds the size limit");
}View on GitHub (pinned to afd2eddd3a)
Solutions
- Populate the recipients list with at least one age recipient before calling encrypt_bytes.
- Check that age identity/recipient configuration is present and parses (e.g. ssh-ed25519 or age1... recipients).
- Verify the plugin registration/recovery path actually supplies its software-recovery recipient.
- In tests, add a generated recipient (as the passing tests do) before encrypting.
Example fix
// before let recipients: Vec<Box<dyn Recipient + Send>> = vec![]; encrypt_bytes(&plaintext, &recipients)?; // after let recipients: Vec<Box<dyn Recipient + Send>> = vec![Box::new(recipient)]; assert!(!recipients.is_empty()); encrypt_bytes(&plaintext, &recipients)?;
Defensive patterns
Strategy: validation
Validate before calling
if recipients.is_empty() {
return Err(anyhow!("no age recipients configured; check MISE age recipient settings"));
} Type guard
fn has_recipients(recipients: &[Box<dyn Recipient + Send>]) -> bool { !recipients.is_empty() } Try / catch
match encrypt_bytes(&plaintext, &recipients) { Err(e) if e.to_string().contains("no age recipients") => { load_recipients_from_config()?; }, Err(e) => return Err(e), Ok(ciphertext) => ciphertext } Prevention
- Validate recipient configuration at startup
- Ensure age key files exist and are readable before encrypting
- Add a software-recovery recipient in plugin registration paths
When it happens
Trigger: Calling encrypt_bytes (directly or via plugin protocol encryption / software recovery paths) with an empty recipients slice — e.g. no identities loaded from config, key files missing, or filtering removed all recipients.
Common situations: Missing or unreadable age key files (MISE_AGE_RECIPIENTS / key config); plugin recovery paths where the recipient was never registered; tests constructing encryption without adding a recipient.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- encrypted dotfile {target} requires an external source, not
- trust the configuration before using its encryption recipien
- [history.encryption].recipients must not be empty; configure
- encrypted tracked files require [history.encryption].recipie
- {bin_name} is a mise bin however it is not currently active.
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/48abebd22c10cf8c.
Report an issue: GitHub.