zeroclaw-labs/zeroclaw · error · anyhow::Error

git channel: reading private_key_path `{path}` failed: {e}

Error message

git channel: reading private_key_path `{path}` failed: {e}

What it means

The GitHub provider for the git channel authenticates as a GitHub App using a PEM private key loaded from channels.git.<alias>.private_key_path. When std::fs::read_to_string fails, build_provider bails with the OS error inline. Loose file permissions only produce a warning; an unreadable file is fatal.

Source

Thrown at crates/zeroclaw-channels/src/git/channel.rs:53

/// with configs that predate the inline field.
fn resolve_github_private_key(cfg: &GitConfig) -> anyhow::Result<Option<String>> {
    if cfg.private_key.is_some() {
        return Ok(cfg.private_key.clone());
    }
    let Some(path) = cfg
        .private_key_path
        .as_deref()
        .map(str::trim)
        .filter(|p| !p.is_empty())
    else {
        return Ok(None);
    };
    match std::fs::read_to_string(path) {
        Ok(pem) => {
            warn_on_loose_permissions(path);
            Ok(Some(pem))
        }
        Err(e) => anyhow::bail!("git channel: reading private_key_path `{path}` failed: {e}"),
    }
}

/// The private key is a long-lived credential: group/other access on the
/// key file is operator error worth surfacing, but not worth refusing to
/// start over.
#[cfg(unix)]
fn warn_on_loose_permissions(path: &str) {
    use std::os::unix::fs::MetadataExt;
    let Ok(meta) = std::fs::metadata(path) else {
        return;
    };
    if meta.mode() & 0o077 != 0 {
        ::zeroclaw_log::record!(
            WARN,
            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
                .with_attrs(::serde_json::json!({"path": path})),
            "GitHub App private key is readable by group/other; chmod 600 recommended"

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Use an absolute path in private_key_path
  2. Verify the file exists and is readable by the service user: sudo -u <user> cat <path>
  3. Fix ownership/permissions for the service account (chown, chmod 600)
  4. In containers, confirm the secret mount actually provides the file at that path

Example fix

# before
[channels.git.mygh]
provider = "github"
private_key_path = "keys/app.pem" # relative: breaks when cwd differs

# after
[channels.git.mygh]
provider = "github"
private_key_path = "/etc/zeroclaw/secrets/app.pem" # absolute, mounted, mode 600
Defensive patterns

Strategy: validation

Validate before calling

// Rust — fail fast with a clearer diagnosis before building the provider
let path = std::path::Path::new(&cfg.private_key_path);
if !path.is_absolute() {
    anyhow::bail!("private_key_path should be absolute: {}", path.display());
}
std::fs::read_to_string(path).with_context(|| {
    format!("private key unreadable at {} (running as the service user?)", path.display())
})?;

Prevention

When it happens

Trigger: private_key_path points at a nonexistent file, the service user lacks read permission, or a relative path resolves against an unexpected working directory.

Common situations: Deploying with a relative key path that breaks when the cwd changes; key file owned by root in a container; secret volume not mounted; typo in the path.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/8f251569e308abec. Report an issue: GitHub.