aaif-goose/goose · error
Failed to read client private key: {}
Error message
Failed to read client private key: {} What it means
Companion to the certificate read: TlsConfig::load_identity also reads the client private key PEM from key_path. Any I/O failure on that file (missing, unreadable, non-UTF8) is reported as 'Failed to read client private key: {io_error}' before any identity parsing is attempted.
Source
Thrown at crates/goose-providers/src/api_client.rs:94
self
}
pub fn with_ca_cert(mut self, path: PathBuf) -> Self {
self.ca_cert_path = Some(path);
self
}
pub fn is_configured(&self) -> bool {
self.client_identity.is_some() || self.ca_cert_path.is_some()
}
#[cfg(any(feature = "rustls-tls", feature = "native-tls"))]
fn load_identity(&self) -> Result<Option<Identity>> {
if let Some(cert_key_pair) = &self.client_identity {
let cert_pem = read_to_string(&cert_key_pair.cert_path)
.map_err(|e| anyhow::anyhow!("Failed to read client certificate: {}", e))?;
let key_pem = read_to_string(&cert_key_pair.key_path)
.map_err(|e| anyhow::anyhow!("Failed to read client private key: {}", e))?;
#[cfg(not(feature = "native-tls"))]
let identity = {
let combined_pem = format!("{}\n{}", cert_pem, key_pem);
Identity::from_pem(combined_pem.as_bytes()).map_err(|e| {
anyhow::anyhow!("Failed to create identity from cert and key: {}", e)
})?
};
#[cfg(feature = "native-tls")]
let identity = {
let pkcs8_key_pem = convert_key_to_pkcs8_pem(&key_pem)?;
Identity::from_pkcs8_pem(cert_pem.as_bytes(), pkcs8_key_pem.as_bytes()).map_err(
|e| anyhow::anyhow!("Failed to create identity from cert and key: {}", e),
)?
};
Ok(Some(identity))View on GitHub (pinned to 3810898a74)
Solutions
- Check the exact key_path in the config and ls -l it from the goose process context
- Convert DER keys to PEM: openssl pkey -inform der -in key.der -out key.pem
- Use absolute paths and readable permissions for the goose user
- Confirm the file is a PEM text starting with -----BEGIN ... PRIVATE KEY-----
Example fix
# before client_identity: cert_path: /etc/goose/tls/client-cert.pem key_path: /etc/goose/tls/client-key # DER binary or typo'd name # after client_identity: cert_path: /etc/goose/tls/client-cert.pem key_path: /etc/goose/tls/client-key.pem
Defensive patterns
Strategy: validation
Validate before calling
fn readable_utf8_file(path: &std::path::Path) -> bool {
std::fs::metadata(path).map(|m| m.is_file()).unwrap_or(false)
&& std::fs::read_to_string(path).is_ok()
}
if !readable_utf8_file(std::path::Path::new(&key_path)) {
anyhow::bail!("client key missing/unreadable: {key_path}");
} Type guard
fn key_file_is_pem(path: &std::path::Path) -> bool {
std::fs::read_to_string(path)
.map(|s| s.contains("-----BEGIN") && s.contains("PRIVATE KEY-----"))
.unwrap_or(false)
} Prevention
- Keep cert and key in the same directory so one path fix covers both
- Generate keys as PEM: openssl genpkey or add -outform PEM
- Check permissions as the goose user, not your admin shell
- In Kubernetes, verify secret key names match mounted filenames exactly
When it happens
Trigger: Provider config includes client_identity whose key_path is wrong/unreadable, or the key file is binary DER rather than PEM text. Triggered at client build time; the cert read (previous line) must have succeeded, so the cert path was fine and only the key is at fault.
Common situations: Cert and key in different directories and only the cert path updated; key generated by openssl genrsa outputting PKCS#8 binary without -outform PEM; Kubernetes secret mounted with the key at a slightly different name; file permission 600 owned by another user.
Related errors
- Failed to read client certificate: {}
- Failed to read CA certificate: {}
- Failed to create identity from cert and key: {}
- Failed to parse PEM key: {}
- Failed to create directory: {}
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/9c2c909ae546446c.
Report an issue: GitHub.