gitbutlerapp/gitbutler · error · anyhow::Error
Failed to sign SSH: {stdout} {stderr}
Error message
Failed to sign SSH: {stdout} {stderr} What it means
The SSH signing path runs ssh-keygen -Y sign -n git -f <key> [-U] over a temp file; when the process exits non-zero, this bail surfaces both its stdout and stderr, which contain ssh-keygen's actual complaint (agent issues, unreadable key, unsupported flags). The .sig sidecar file is only read on success.
Source
Thrown at crates/but-core/src/commit/mod.rs:397
.with_context(|| format!("Didn't trust 'user.signingKey': {signing_key}"))?;
signing_cmd
.arg(signing_key)
.arg(buffer_file_to_sign_path.to_path_buf())
};
let output = into_command(signing_cmd)
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.stdin(Stdio::null())
.output()?;
if output.status.success() {
let signature_path = buffer_file_to_sign_path.with_extension("sig");
let sig_data = std::fs::read(signature_path)?;
Ok(BString::new(sig_data))
} else {
let stderr = BString::new(output.stderr);
let stdout = BString::new(output.stdout);
bail!("Failed to sign SSH: {stdout} {stderr}");
}
} else {
let gpg_program = match config.trusted_path("gpg.program")? {
Some(program) if !program.as_os_str().is_empty() => program,
_ => Path::new("gpg").into(),
};
let mut cmd = into_command(
prepare_with_shell_on_windows(&gpg_program)
.args(["--status-fd=2", "-bsau"])
.arg(gix::path::from_bstring(signing_key))
.arg("-"),
);
cmd.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::piped());
let mut child = match cmd.spawn() {View on GitHub (pinned to caf1f223d3)
Solutions
- Read the embedded stderr — ssh-keygen states the exact failure (e.g. 'agent refused operation', 'no such file')
- Ensure ssh-agent is running and the key is loaded: eval $(ssh-agent -s) && ssh-add <key>, then retry the commit
- Verify user.signingkey (path or literal key) and that gpg.format=ssh plus gpg.ssh.program point to a working ssh-keygen
- Upgrade OpenSSH to >= 8.0 if -Y sign is unsupported; test manually: ssh-keygen -Y sign -n git -f <key> file
Example fix
# reproduce the failure the commit path sees printf test > /tmp/f && ssh-keygen -Y sign -n git -f ~/.ssh/id_ed25519 /tmp/f # fix: load the key into the agent the signer (-U) requires eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519 git commit -S -m 'signed'
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight the signer exactly the way the commit path will use it
fn ssh_sign_ok(key: &Path) -> bool {
std::process::Command::new("ssh-keygen")
.args(["-Y", "sign", "-n", "git", "-f"]).arg(key)
.arg(tempfile::NamedTempFile::new().unwrap().path())
.stdin(Stdio::null()).output()
.map(|o| o.status.success()).unwrap_or(false)
} Try / catch
// the message already carries ssh-keygen output — branch on its content
match sign_buffer(&repo, &buffer).await {
Err(e) => {
let m = e.to_string();
if m.contains("agent refused") || m.contains("no such identity") {
return Err(e.context("run: ssh-add <your-key> (agent must hold the key for -U)"));
}
Err(e)
}
s => s?,
} Prevention
- Ensure SSH_AUTH_SOCK is set and the key is ssh-add'ed in the environment the app runs in (GUI launchers strip it)
- Verify 'git config gpg.format' is ssh and user.signingkey resolves to an existing file or literal key
- Smoke-test with ssh-keygen -Y sign -n git in CI before enabling signed commits
When it happens
Trigger: Committing with gpg.format=ssh when the private key is not loaded in ssh-agent (the -U flag requires the key in the agent), user.signingkey points to a missing/unreadable key file, the key is passphrase-protected without an agent, or an ssh-keygen too old for -Y signing (< OpenSSH 8.0) or missing gpg.ssh.program support.
Common situations: headless CI without SSH_AUTH_SOCK; machines where the agent starts after the app; gpg.ssh.program set to a wrapper that fails; literal key vs file-path signingkey confusion; Windows OpenSSH shipping without ssh-keygen -Y support.
Related errors
- Could not find '{}'. Please make sure it is in your `PATH` o
- The repository at {} has no working directory. GitButler req
- Failed to communicate with LM Studio server: ${error instanc
- Invalid response: ${JSON.stringify(result)}
- When using GitButler's API to summarize code, you must be lo
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/4a37683361bf4cf0.
Report an issue: GitHub.