GitoxideLabs/gitoxide · error

prepared commits have an editor

Error message

prepared commits have an editor

What it means

An `Option::expect` panic on `prepared.editor` in the `tix new` command. When no explicit `-m` message is given, `tix new` relies on `edit::create::prepare()` always providing an editor handle (`gix::command::Prepare`) so the commit message can be composed interactively; the `expect` asserts this invariant. Panicking means prepare regressed and returned `Prepared { editor: None }`, i.e. a bug in the library, not a user mistake.

Solutions

  1. Inspect `edit::create::prepare_inner` and ensure every return path sets `editor` when no explicit message was supplied.
  2. Make the missing editor a proper error instead of a panic: return a message telling the user to set `GIT_EDITOR`/`EDITOR`, or use the fallback editor path.
  3. Keep the explicit-message path separate (as it already is) so `prepare` is only required to spawn an editor on the interactive path.
  4. Reproduce with the `tix new` tests (`explicit_message_*`, `index_and_worktree_*`) to pin down which prepare path leaves the editor `None`.

Example fix

// before
let editor = prepared.editor.take().expect("prepared commits have an editor");
// after
let editor = prepared.editor.take().ok_or_else(|| {
    anyhow::anyhow!("commit preparation did not provide an editor; set GIT_EDITOR or EDITOR")
})?;
Defensive patterns

Strategy: validation

Validate before calling

let editor = match prepared.editor.take() {
    Some(editor) => editor,
    None => anyhow::bail!("commit preparation produced no editor; set GIT_EDITOR or EDITOR, or pass -m <message>"),
};

Type guard

fn has_editor(prepared: &crate::edit::create::Prepared) -> bool {
    prepared.editor.is_some()
}

Try / catch

match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| run_new(&repository_path, bare, args))) {
    Ok(result) => result,
    Err(_panic) => anyhow::bail!("tix new panicked: prepared commit lacked an editor (library bug)"),
}

Prevention

When it happens

Trigger: Running `tix new` without a message argument (interactive path) in a build where `edit::create::prepare`/`prepare_inner` skipped spawning the editor — e.g. after a refactor made editor setup conditional or failure silently non-fatal.

Common situations: Developer builds after changing how editors are spawned (missing `GIT_EDITOR`/`EDITOR` handling in prepare), CI sandboxes without a terminal where a new early-return path leaves `editor` unset, or patched builds that disable editor launch.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at gix-tix/src/command/new.rs:61

        .as_deref()
        .map(gix::path::os_str_into_bstr)
        .transpose()
        .context("author is not valid UTF-8")?;
    let repository_path = repository.git_dir().to_owned();
    let bare = repository.is_bare();
    let mut prepared = crate::edit::create::prepare_from(repository, parent, source, author, args.todo)?;
    if prepared.is_empty && !args.allow_empty {
        anyhow::bail!("the new commit would be empty; use --allow-empty to create it anyway");
    }

    let explicit = super::reword::explicit_message(&args.edit, std::io::stdin())?;
    let outcome = if let Some(message) = explicit {
        let mut repository = crate::open_repository(&repository_path, bare, false)
            .context("could not reopen repository before creating commit")?;
        repository.object_cache_size(None);
        crate::edit::create::apply_message_reporting(repository, &graph, prepared, &message)?
    } else {
        let editor = prepared.editor.take().expect("prepared commits have an editor");
        let Some(edited) = crate::edit::edit_document_without_terminal(
            editor,
            &prepared.document,
            &format!("tix-commit-{}.md", std::process::id()),
        )?
        else {
            println!("no commit created: no input was provided");
            return Ok(());
        };
        let mut repository = crate::open_repository(&repository_path, bare, false)
            .context("could not reopen repository after editing commit")?;
        repository.object_cache_size(None);
        crate::edit::create::apply_reporting(repository, &graph, prepared, &edited)?
    };
    let repository = crate::open_repository(&repository_path, bare, false)
        .context("could not reopen repository after creating commit")?;
    let selected = outcome
        .selected

View on GitHub (pinned to e73179060b)