GitoxideLabs/gitoxide · error

Commit at has an invalid or untrusted signature

Error message

Commit at {rev_spec} has an invalid or untrusted signature

What it means

`verify_commit` ran GPG verification of the commit's signature; either the signature check completed but the result was invalid/untrusted (`outcome.is_valid() == false`), after stderr already received gpg's raw output. Distinguishes from the unsigned case, which raises a separate error.

Solutions

  1. Import the signer's public key (`gpg --import` or from a keyserver) and re-verify
  2. Check for expired/revoked keys and refresh (`gpg --refresh-keys`)
  3. Inspect the gpg output on stderr, which this function writes before failing, for the exact failure reason
  4. If the signature is genuinely bad, treat the commit as untrusted — do not bypass

Example fix

// before
verify(repo, rev_spec, &mut out)?;
// after
match verify(repo, rev_spec, &mut out) {
    Ok(()) => println!("signature OK"),
    Err(e) if e.to_string().contains("invalid or untrusted signature") => {
        eprintln!("commit signature invalid — fetch signer key and retry");
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check the key exists locally
let out = std::process::Command::new("gpg").args(["--list-keys"]).output()?;
if !out.status.success() { anyhow::bail!("gpg keyring not initialized"); }

Try / catch

match verify(repo, rev_spec, &mut err) {
    Ok(()) => {},
    Err(e) if e.to_string().contains("is not signed") => eprintln!("commit unsigned"),
    Err(e) => return Err(e.into_error()),
}

Prevention

When it happens

Trigger: Calling `verify` on a commit whose signature fails cryptographic validation, was made with an unknown/expired/revoked key, or whose trust level is insufficient.

Common situations: Missing or expired GPG keys in the local keyring; signed commits from contributors whose keys aren't imported; verifying on a machine without `gpg` configured; pinentry issues in non-interactive CI.

Related errors


AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/2cba2a14247ce688. Report an issue: GitHub.

Appendix: source

Thrown at gitoxide-core/src/repository/commit.rs:28

    objs::commit::SIGNATURE_FIELD_NAME,
};

/// Note that this is a quick implementation of commit signature verification that ignores a lot of what
/// git does and can do, while focussing on the gist of it.
/// For this to go into `gix`, one will have to implement many more options and various validation programs.
pub fn verify(repo: gix::Repository, rev_spec: Option<&str>) -> Result<()> {
    let rev_spec = rev_spec.unwrap_or("HEAD");
    let commit = repo
        .rev_parse_single(format!("{rev_spec}^{{commit}}").as_str())?
        .object()?
        .into_commit();
    let outcome = commit
        .verify_signature()
        .context("Could not verify commit signature")?
        .ok_or_else(|| anyhow!("Commit at {rev_spec} is not signed"))?;
    std::io::stderr().write_all(&outcome.output)?;
    if !outcome.is_valid() {
        bail!("Commit at {rev_spec} has an invalid or untrusted signature");
    }
    Ok(())
}

/// Note that this is a quick first prototype that lacks some of the features provided by `git verify-commit`.
pub fn sign(repo: gix::Repository, rev_spec: Option<&str>, mut out: impl std::io::Write) -> Result<()> {
    let rev_spec = rev_spec.unwrap_or("HEAD");
    let object = repo
        .rev_parse_single(format!("{rev_spec}^{{commit}}").as_str())?
        .object()?;
    let mut commit_ref = object.to_commit_ref();
    if commit_ref.extra_headers().pgp_signature().is_some() {
        gix::trace::info!("The commit {id} is already signed, did nothing", id = object.id);
        writeln!(out, "{id}", id = object.id)?;
        return Ok(());
    }

    let mut cmd: std::process::Command = gix::command::prepare("gpg").into();

View on GitHub (pinned to e73179060b)