GitoxideLabs/gitoxide · error · anyhow::Error

external diff exited with

Error message

external diff exited with {status}

What it means

The configured external diff program (diff.external / GIT_EXTERNAL_DIFF) exited with a status other than 0 or 1. Exit 1 is tolerated because diff tools conventionally use it to mean 'files differ'; anything else indicates the tool itself failed.

Solutions

  1. Run the external diff command manually with the same arguments to see its real error.
  2. Fix or replace diff.external in config (or unset GIT_EXTERNAL_DIFF) and re-run.
  3. Make your custom diff tool return 0 (same) or 1 (differ) and reserve other codes for real failures.
  4. If the tool crashes on this input, test it on the specific files involved.

Example fix

// before
#!/bin/sh
compare "$@"  # exit 2 on usage errors
// after
#!/bin/sh
compare "$@" || exit 1  # map failures to diff-style exit codes
Defensive patterns

Strategy: validation

Validate before calling

let cmd = repo.config_snapshot().trusted_program(gix::config::tree::Diff::EXTERNAL)?;
// test it: run cmd with two temp files and require exit 0 or 1

Try / catch

match show_external_diff(...) {
    Err(err) if err.to_string().starts_with("external diff exited with") => {
        eprintln!("{}; falling back to built-in diff", err);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Running a file diff through the external diff command, which exits with e.g. 2 (usage error), 127 (command shell issue), 127/126, or a crash exit code.

Common situations: Misconfigured diff.external pointing to a broken script; external tool missing dependencies; tool crashing on binary/large files; wrong argument conventions.

Related errors


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

Appendix: source

Thrown at gix-tix/src/lib.rs:6099

        for reference in &group.references {
            let mut refspec = b":".to_vec();
            refspec.extend_from_slice(reference.as_bstr());
            command.arg(gix::path::from_bstr(refspec.as_bstr()).as_ref());
        }
        match command.status() {
            Ok(status) if status.success() => outcome.deleted += group.references.len(),
            Ok(status) => outcome.failures.push(format!("{} exited with {status}", group.remote)),
            Err(err) => outcome.failures.push(format!("{}: {err}", group.remote)),
        }
    }
    outcome
}

fn external_diff_status(status: ExitStatus) -> Result<()> {
    if status.success() || status.code() == Some(1) {
        Ok(())
    } else {
        anyhow::bail!("external diff exited with {status}")
    }
}

fn pager_write_result(result: io::Result<()>) -> Result<()> {
    match result {
        Err(err) if err.kind() == io::ErrorKind::BrokenPipe => Ok(()),
        result => result.context("could not write diff to pager"),
    }
}

fn pager_status(status: ExitStatus) -> Result<()> {
    if status.success() {
        Ok(())
    } else {
        anyhow::bail!("diff pager exited with {status}")
    }
}

View on GitHub (pinned to e73179060b)