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

  1. Read the embedded stderr — ssh-keygen states the exact failure (e.g. 'agent refused operation', 'no such file')
  2. Ensure ssh-agent is running and the key is loaded: eval $(ssh-agent -s) && ssh-add <key>, then retry the commit
  3. Verify user.signingkey (path or literal key) and that gpg.format=ssh plus gpg.ssh.program point to a working ssh-keygen
  4. 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

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


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/4a37683361bf4cf0. Report an issue: GitHub.