{"record":{"id":"1b68a360e479c375","repo":"jdx/mise","slug":"no-public-key-for-the-ssh-private-key-at-expec","errorCode":null,"errorMessage":"no public key for the SSH private key at {}; expected {}","messagePattern":"no public key for the SSH private key at (.+?); expected (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/agecrypt.rs","lineNumber":298,"sourceCode":"        .map_err(|e| eyre!(\"age recipient plugin is unavailable: {e}\"))?;\n        Ok(Some(Box::new(plugin)))\n    } else {\n        Ok(None)\n    }\n}\n\n/// The public key beside an SSH private key: age cannot derive it, so the\n/// `.pub` file must exist.\npub(crate) async fn ssh_public_key_for_private(path: &Path) -> Result<String> {\n    let pub_path = path.with_extension(\"pub\");\n    if pub_path.exists() {\n        let content = file::read_to_string(&pub_path)?;\n        let trimmed = content.trim();\n        if trimmed.starts_with(\"ssh-\") {\n            return Ok(trimmed.to_string());\n        }\n    }\n    bail!(\n        \"no public key for the SSH private key at {}; expected {}\",\n        display_path(path),\n        display_path(&pub_path)\n    )\n}\n\n/// Every identity this machine has: `MISE_AGE_KEY`, the identity files\n/// named by the settings and the default `age.txt`, and the SSH keys named\n/// by the settings and the default `~/.ssh/id_ed25519` / `id_rsa`.\npub(crate) async fn load_all_identities() -> LoadedIdentities {\n    load_identities(false).await\n}\n\nasync fn load_identities(interactive: bool) -> LoadedIdentities {\n    let identity_files = get_all_identity_files().await;\n    let ssh_identity_files = get_all_ssh_identity_files();\n    let mut loaded = LoadedIdentities::default();\n    let mut plugin_sources = Vec::new();","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/agecrypt.rs#L280-L316","documentation":"ssh_public_key_for_private derives an SSH recipient from a private key path by reading the sibling .pub file; it accepts the contents only if they start with \"ssh-\". If no sibling public key exists, or its contents are not a standard OpenSSH public key line, it bails naming both the private key and the expected .pub path.","triggerScenarios":"load_ssh_recipient_from_path or default_recipient_strings is given a private key whose corresponding <path>.pub is missing, unreadable, empty, or in a non-OpenSSH format (not starting with \"ssh-\", e.g. an RFC4716 or PEM export).","commonSituations":"User passes ~/.ssh/id_rsa but never generated id_rsa.pub; the .pub file was deleted or renamed; keys converted from PuTTY/PEM with a non-standard public-key blob.","solutions":["Generate the public key next to the private key: ssh-keygen -y -f <private> > <private>.pub.","Ensure the .pub file contains a single OpenSSH line beginning with \"ssh-\" (e.g. ssh-ed25519 AAAA...).","Point mise at a key pair whose .pub exists, or specify the public key explicitly as the recipient."],"exampleFix":"// before\nmise uses the key at ~/.ssh/id_rsa, but id_rsa.pub is missing\n// error: no public key for the SSH private key at ~/.ssh/id_rsa; expected ~/.ssh/id_rsa.pub\n// after\n$ ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub\n$ mise ... # now finds a valid ssh- prefixed public key","handlingStrategy":"validation","validationCode":"use std::path::Path;\nfn ssh_pair_ready(priv_key: &Path) -> bool {\n    let pub_path = priv_key.with_file_name(format!(\"{}.pub\", priv_key.file_name().unwrap().to_string_lossy()));\n    std::fs::read_to_string(&pub_path)\n        .map(|c| c.trim().starts_with(\"ssh-\"))\n        .unwrap_or(false)\n}","typeGuard":"fn is_openssh_pubkey(s: &str) -> bool { s.trim().starts_with(\"ssh-\") }","tryCatchPattern":"match ssh_public_key_for_private(&key_path) {\n    Ok(pk) => use(pk),\n    Err(e) if e.to_string().contains(\"no public key\") => {\n        let _ = std::process::Command::new(\"ssh-keygen\")\n            .args([\"-y\", \"-f\"]).arg(&key_path)\n            .stdout(std::fs::File::create(key_path.with_extension(\"pub\"))?)\n            .status()?;\n        ssh_public_key_for_private(&key_path)?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always generate key pairs with ssh-keygen so the .pub sibling exists.","Ensure .pub files are OpenSSH-format single lines starting with \"ssh-\".","Convert non-OpenSSH keys (PEM/PuTTY) before using them as age/SSH recipients."],"tags":["ssh","keys","file-not-found"],"backgroundTag":"file-not-found","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}