gitbutlerapp/gitbutler · error

Editor exited with non-zero status

Error message

Editor exited with non-zero status

What it means

When building a commit message from an external editor (`from_external_editor`), the CLI spawns the editor via a shell, inherits stdio, and waits for it. If the editor process exits with a non-zero status, this error (with code EditorExitedWithNonZeroStatus) is returned instead of reading the temp file.

Solutions

  1. Set a known-good editor: `export EDITOR=vim` (or nano/code) and retry.
  2. In vim, exit normally with :wq instead of :cq.
  3. Verify the editor binary exists and is executable (`which $EDITOR`).
  4. If the failure persists, write the message directly with a non-interactive flag (e.g. `but commit -m "message"`) to bypass the editor.

Example fix

// before
EDITOR=emacsclient git-style editor launch fails
// after
export EDITOR="emacsclient -a nano"
but commit
Defensive patterns

Strategy: try-catch

Validate before calling

const ed = process.env.EDITOR || process.env.GIT_EDITOR;
if (!ed || !commandExists(ed.split(' ')[0])) console.warn(`editor '${ed}' not found; set EDITOR to an installed editor`);

Try / catch

match CommitMessage::from_editor(&tempfile) {
  Err(e) if e.to_string().contains("non-zero status") => {
    eprintln!("editor failed; check EDITOR and exit the editor normally (:wq, not :cq)");
    Err(e)
  }
  other => other,
}

Prevention

When it happens

Trigger: The configured GIT_EDITOR/core.editor exits non-zero — editor crashed, user aborted in vim with :cq, editor binary not found under the invoked name, or the editor wrote to a read-only temp file.

Common situations: vim/nvim `:cq` usage; bad EDITOR variable pointing to a non-executable or missing program; editor opening a GUI and failing silently; shell wrapper quoting issues.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/b8ac382f7bb96cd6. Report an issue: GitHub.

Appendix: source

Thrown at crates/but/src/tui/get_text.rs:120

        if !initial_text.ends_with('\n') {
            writeln!(&mut tempfile)?;
        }
        writeln!(&mut tempfile, "{REST_TEXT_MARKER}")?;
        writeln!(&mut tempfile, "{rest_text}")?;
    }

    // The editor command is allowed to be a shell expression, e.g. "code --wait" is somewhat common.
    // We need to execute within a shell to make sure we don't get "No such file or directory" errors.
    let status = gix::command::prepare(editor_cmd)
        .arg(tempfile.path())
        .stdin(std::process::Stdio::inherit())
        .stdout(std::process::Stdio::inherit())
        .with_shell()
        .spawn()?
        .wait()?;

    if !status.success() {
        return Err(anyhow::anyhow!("Editor exited with non-zero status")
            .context(Code::EditorExitedWithNonZeroStatus));
    }

    Ok(std::fs::read(&tempfile)
        .context("failed to read contents of commit message file")?
        .into())
}

/// Launch the built-in TUI editor.
fn from_builtin_editor(
    filename_safe_intent: &str,
    initial_text: &str,
    rest_text: Option<&str>,
) -> Result<BString> {
    // Determine editor mode based on the intent
    let mode = if filename_safe_intent.contains("commit") {
        super::editor::EditorMode::CommitMessage
    } else if filename_safe_intent.contains("branch") {

View on GitHub (pinned to 58e5313667)