{"record":{"id":"7f349970fa3c243f","repo":"GitoxideLabs/gitoxide","slug":"prepared-commits-have-an-editor","errorCode":null,"errorMessage":"prepared commits have an editor","messagePattern":"prepared commits have an editor","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-tix/src/command/new.rs","lineNumber":61,"sourceCode":"        .as_deref()\n        .map(gix::path::os_str_into_bstr)\n        .transpose()\n        .context(\"author is not valid UTF-8\")?;\n    let repository_path = repository.git_dir().to_owned();\n    let bare = repository.is_bare();\n    let mut prepared = crate::edit::create::prepare_from(repository, parent, source, author, args.todo)?;\n    if prepared.is_empty && !args.allow_empty {\n        anyhow::bail!(\"the new commit would be empty; use --allow-empty to create it anyway\");\n    }\n\n    let explicit = super::reword::explicit_message(&args.edit, std::io::stdin())?;\n    let outcome = if let Some(message) = explicit {\n        let mut repository = crate::open_repository(&repository_path, bare, false)\n            .context(\"could not reopen repository before creating commit\")?;\n        repository.object_cache_size(None);\n        crate::edit::create::apply_message_reporting(repository, &graph, prepared, &message)?\n    } else {\n        let editor = prepared.editor.take().expect(\"prepared commits have an editor\");\n        let Some(edited) = crate::edit::edit_document_without_terminal(\n            editor,\n            &prepared.document,\n            &format!(\"tix-commit-{}.md\", std::process::id()),\n        )?\n        else {\n            println!(\"no commit created: no input was provided\");\n            return Ok(());\n        };\n        let mut repository = crate::open_repository(&repository_path, bare, false)\n            .context(\"could not reopen repository after editing commit\")?;\n        repository.object_cache_size(None);\n        crate::edit::create::apply_reporting(repository, &graph, prepared, &edited)?\n    };\n    let repository = crate::open_repository(&repository_path, bare, false)\n        .context(\"could not reopen repository after creating commit\")?;\n    let selected = outcome\n        .selected","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-tix/src/command/new.rs#L43-L79","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect `edit::create::prepare_inner` and ensure every return path sets `editor` when no explicit message was supplied.","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.","Keep the explicit-message path separate (as it already is) so `prepare` is only required to spawn an editor on the interactive path.","Reproduce with the `tix new` tests (`explicit_message_*`, `index_and_worktree_*`) to pin down which prepare path leaves the editor `None`."],"exampleFix":"// before\nlet editor = prepared.editor.take().expect(\"prepared commits have an editor\");\n// after\nlet editor = prepared.editor.take().ok_or_else(|| {\n    anyhow::anyhow!(\"commit preparation did not provide an editor; set GIT_EDITOR or EDITOR\")\n})?;","handlingStrategy":"validation","validationCode":"let editor = match prepared.editor.take() {\n    Some(editor) => editor,\n    None => anyhow::bail!(\"commit preparation produced no editor; set GIT_EDITOR or EDITOR, or pass -m <message>\"),\n};","typeGuard":"fn has_editor(prepared: &crate::edit::create::Prepared) -> bool {\n    prepared.editor.is_some()\n}","tryCatchPattern":"match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| run_new(&repository_path, bare, args))) {\n    Ok(result) => result,\n    Err(_panic) => anyhow::bail!(\"tix new panicked: prepared commit lacked an editor (library bug)\"),\n}","preventionTips":["Test the interactive `tix new` path (no -m) in environments with and without a terminal/editor","Ensure prepare_inner never returns early without spawning the editor when no explicit message is given","Use ok_or_else with an actionable message instead of expect for editor Option fields","Fall back to a default editor (e.g. vi or GIT_EDITOR lookup) before failing"],"tags":["rust","panic","editor","internal-invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}