gitbutlerapp/gitbutler · warning

Editor exited with non-zero status

Error message

Editor exited with non-zero status

What it means

Thrown by the legacy `but reword` flow when a commit message is edited in the user's Git editor. `get_commit_message_from_editor_legacy` launches the configured editor, and when the editor process exits with a non-zero status the underlying `but_error::Code::EditorExitedWithNonZeroStatus` is flattened into this plain anyhow message. A non-zero editor exit is the standard Git convention for deliberately aborting an edit (e.g. `:cq` in vim), so this usually means 'user cancelled', not corruption.

Source

Thrown at crates/but/src/command/legacy/reword2.rs:326

fn edit_commit_message(
    repo: &gix::Repository,
    context_lines: u32,
    commit_details: CommitDetails,
    editor_initial_message: &str,
    current_message_for_comparison: &str,
) -> anyhow::Result<Option<String>> {
    get_commit_message_from_editor_legacy(
        repo,
        context_lines,
        commit_details,
        editor_initial_message.to_owned(),
        current_message_for_comparison,
        ShowDiffInEditor::Unspecified,
    )
    .map_err(|err| {
        if let Some(Code::EditorExitedWithNonZeroStatus) = err.downcast_ref::<but_error::Code>() {
            anyhow::anyhow!("Editor exited with non-zero status")
        } else {
            err
        }
    })
}

pub enum RewordOperation {
    Commit {
        target: CommitId,
        new_message: CommitMessageSource,
    },
    FormatCommit {
        target: CommitId,
    },
    Branch {
        target: FullName,
        new_name: BranchNameSource,
    },

View on GitHub (pinned to caf1f223d3)

Solutions

  1. If the abort was intentional, just rerun the command and either save the buffer or pass the message directly with the -m/--message flag
  2. Test the configured editor outside `but`: `git var GIT_EDITOR` then launch that command by hand to confirm it exits 0 when saved
  3. In scripts and CI, always pass -m "new message" so no editor is spawned at all
  4. In wrappers, treat this specific error as a cancel (exit quietly) instead of surfacing a stack trace

Example fix

# before: opens an editor that may exit non-zero
but reword c3

# after: non-interactive, no editor involved
but reword c3 -m "fix: correct the commit message"
Defensive patterns

Strategy: try-catch

Validate before calling

let editor_spec = std::env::var("GIT_EDITOR")
    .or_else(|_| std::env::var("VISUAL"))
    .or_else(|_| std::env::var("EDITOR"))
    .unwrap_or_else(|_| "vi".to_string());
let bin = editor_spec.split_whitespace().next().unwrap_or("");
if which::which(bin).is_err() {
    eprintln!("configured editor '{bin}' is not on PATH; pass -m or fix core.editor");
}

Type guard

fn is_editor_abort(err: &anyhow::Error) -> bool {
    matches!(
        err.downcast_ref::<but_error::Code>(),
        Some(but_error::Code::EditorExitedWithNonZeroStatus)
    ) || err.to_string() == "Editor exited with non-zero status"
}

Try / catch

match run_reword_with_editor(&mut ctx) {
    Ok(Some(message)) => { /* use message */ }
    Ok(None) => { /* unchanged */ }
    Err(err) if is_editor_abort(&err) => {
        // user cancelled in the editor; not a failure
        return Ok(());
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Running the legacy reword command with the Editor message source (no `-m` supplied) while GIT_EDITOR / core.editor / EDITOR returns a non-zero exit code: quitting vim with `:cq`, a VS Code `--wait` wrapper exiting 1, an editor killed by a signal, or a broken editor binary.

Common situations: User intentionally aborts the message edit with `:cq`; EDITOR points to a wrapper script that always exits non-zero; running reword in CI or a non-TTY session where the editor fails immediately; CI treated the abort as a hard failure.

Related errors


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