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

  1. Configure at least one age recipient public key (age1... / ssh-ed25519) before encrypting
  2. Check that the key file or env var feeding recipient loading is non-empty and parsed correctly
  3. In plugin protocol usage, ensure the peer returned its recovery recipients during the handshake
  4. 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

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


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)