jdx/mise · error
no age recipients to encrypt for
Error message
no age recipients to encrypt for
What it means
encrypt_bytes requires at least one age recipient (public key) to encrypt for. An empty recipients slice would produce ciphertext no one can decrypt, so the function fails fast with this error before doing any work.
Solutions
- Configure at least one age recipient public key (age1... / ssh-ed25519) before encrypting
- Check that the key file or env var feeding recipient loading is non-empty and parsed correctly
- In plugin protocol usage, ensure the peer returned its recovery recipients during the handshake
- Validate the recipients list length at the call site and surface a clearer message to the user
Example fix
// before let recipients: Vec<Box<dyn Recipient + Send>> = load_recipients()?; // may be empty let ct = encrypt_bytes(&plaintext, &recipients)?; // after let recipients = load_recipients()?; anyhow::ensure!(!recipients.is_empty(), "no age recipients configured"); let ct = encrypt_bytes(&plaintext, &recipients)?;
Defensive patterns
Strategy: validation
Validate before calling
// Rust (caller-side check) anyhow::ensure!(!recipients.is_empty(), "configure at least one age recipient (MISE_AGE_RECIPIENTS)"); let ct = encrypt_bytes(&plaintext, &recipients)?;
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") => {
eprintln!("set your age public key before encrypting");
}
other => other,
} Prevention
- Validate recipient configuration at startup
- Fail loudly when key loading yields zero keys instead of passing empty slices
- Test the encrypt path in CI with a real test key
When it happens
Trigger: Calling encrypt_bytes with recipients == &[] — e.g. no identities/keys were configured, key loading produced zero recipients, or a plugin protocol call passed an empty recipient list.
Common situations: Missing or empty MISE_AGE_RECIPIENTS-style config, age public key file empty or not parsed, plugin protocol handshake returned no recovery recipients, variable holding the key never populated.
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
- compressed payload exceeds the size limit
- dotfiles: cannot enroll encrypted paths while history is…
- encrypted tracked files require…
- [experimental] No age recipients provided. Use…
- [history.encryption].recipients must not be empty…
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/48abebd22c10cf8c.
Report an issue: GitHub.
Appendix: 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 533346cc37)