affaan-m/ECC · error · anyhow::Error

git commit failed: {stderr}

Error message

git commit failed: {stderr}

What it means

commit_staged runs `git commit -m <message>` after the pre-flight checks pass. A non-zero exit re-throws git's stderr. The message has already passed the empty-check and staged-changes check, so failure here is typically a hook, signing, or identity problem.

Source

Thrown at ecc2/src/worktree/mod.rs:472

pub fn commit_staged(worktree: &WorktreeInfo, message: &str) -> Result<String> {
    let message = message.trim();
    if message.is_empty() {
        anyhow::bail!("commit message cannot be empty");
    }
    if !has_staged_changes(worktree)? {
        anyhow::bail!("no staged changes to commit");
    }

    let output = Command::new("git")
        .arg("-C")
        .arg(&worktree.path)
        .args(["commit", "-m", message])
        .output()
        .context("Failed to create commit")?;
    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("git commit failed: {stderr}");
    }

    let rev_parse = Command::new("git")
        .arg("-C")
        .arg(&worktree.path)
        .args(["rev-parse", "--short", "HEAD"])
        .output()
        .context("Failed to resolve commit hash")?;
    if !rev_parse.status.success() {
        let stderr = String::from_utf8_lossy(&rev_parse.stderr);
        anyhow::bail!("git rev-parse failed: {stderr}");
    }

    Ok(String::from_utf8_lossy(&rev_parse.stdout)
        .trim()
        .to_string())
}

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Read the stderr in the bail message — it usually names the exact hook or signing failure; fix that root cause first.
  2. Ensure `user.name` and `user.email` are set for the environment (`git config user.email`).
  3. If a pre-commit hook is expected to fail, surface its output to the user instead of opaque retry.
  4. Re-run `has_staged_changes` immediately before commit to close the race window.

Example fix

// before
let hash = commit_staged(&worktree, msg)?;

// after
match commit_staged(&worktree, msg) {
    Ok(hash) => Ok(hash),
    Err(e) => Err(e).with_context(|| "commit rejected by git (hook/signing/identity); see stderr above"),
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: identity must be configured or git refuses to commit.
fn git_identity_ok(worktree: &WorktreeInfo) -> bool {
    let out = std::process::Command::new("git")
        .arg("-C").arg(&worktree.path)
        .args(["config", "user.email"])
        .output();
    matches!(out, Ok(o) if o.status.success() && !o.stdout.is_empty())
}

if !git_identity_ok(&worktree) {
    anyhow::bail!("set git user.email and user.name before committing");
}
let hash = commit_staged(&worktree, msg)?;

Try / catch

match commit_staged(&worktree, msg) {
    Ok(hash) => Ok(hash),
    Err(e) => {
        let m = format!("{e:#}");
        if m.contains("hook") || m.contains("pre-commit") {
            // surface hook output verbatim; do not retry
        } else if m.contains("user.email") || m.contains("user.name") {
            // prompt to configure identity
        } else if m.contains("index.lock") {
            // clear lock and retry once
        }
        Err(e)
    }
}

Prevention

When it happens

Trigger: A pre-commit or commit-msg hook exits non-zero; GPG signing requested but no usable key/gpg-agent; `user.email`/`user.name` not configured and git refuses to synthesize an identity; `.git/index.lock` contention; a concurrent commit consumed the staged changes between the check and the commit.

Common situations: husky/lefthook running lint+test that fails; CI environment with no `git config --global user.email`; GPG card unplugged; two sessions committing the same worktree simultaneously.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/b3c9284056473398. Report an issue: GitHub.