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
- Use an absolute path in private_key_path
- Verify the file exists and is readable by the service user: sudo -u <user> cat <path>
- Fix ownership/permissions for the service account (chown, chmod 600)
- 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
- Always use absolute paths for credential files
- Run a preflight read of every configured credential file at deploy time
- Mount secrets read-only at fixed paths and verify the mount before start
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
- git channel has no repositories to poll; set `repos` or gran
- git channel provider `{provider}` requires channels.git.<ali
- unknown git channel provider `{other}` (supported: github, g
- git channel `events` routing table ignores every event type;
- grok_cli working_directory must identify a directory
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/8f251569e308abec.
Report an issue: GitHub.