From 02dc3ea4c3d6f1daa0a2eb89dc7e17e3a9582fba Mon Sep 17 00:00:00 2001 From: Anas Khan Date: Mon, 24 Aug 2026 22:20:40 +0530 Subject: [PATCH] cmd: guard empty editor before indexing fields (#17067) Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com> --- cmd/interactive.go | 10 ++++++---- cmd/interactive_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/cmd/interactive.go b/cmd/interactive.go index ea1ed9841..5a2f28c33 100644 --- a/cmd/interactive.go +++ b/cmd/interactive.go @@ -653,9 +653,12 @@ func editInExternalEditor(content string) (string, error) { } // Check that the editor binary exists - name := strings.Fields(editor)[0] - if _, err := exec.LookPath(name); err != nil { - return "", fmt.Errorf("editor %q not found, set OLLAMA_EDITOR to the path of your preferred editor", name) + args := strings.Fields(editor) + if len(args) == 0 { + return "", fmt.Errorf("no editor configured, set OLLAMA_EDITOR to the path of your preferred editor") + } + if _, err := exec.LookPath(args[0]); err != nil { + return "", fmt.Errorf("editor %q not found, set OLLAMA_EDITOR to the path of your preferred editor", args[0]) } tmpFile, err := os.CreateTemp("", "ollama-prompt-*.txt") @@ -672,7 +675,6 @@ func editInExternalEditor(content string) (string, error) { } tmpFile.Close() - args := strings.Fields(editor) args = append(args, tmpFile.Name()) cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin diff --git a/cmd/interactive_test.go b/cmd/interactive_test.go index 7a042d77d..7e428e69b 100644 --- a/cmd/interactive_test.go +++ b/cmd/interactive_test.go @@ -85,6 +85,39 @@ func TestExtractFileDataRemovesQuotedFilepath(t *testing.T) { assert.Equal(t, cleaned, "before after") } +func TestEditInExternalEditorWhitespaceOnly(t *testing.T) { + // A whitespace-only VISUAL or EDITOR is non-empty, so it bypasses the + // editor == "" fallbacks, but strings.Fields collapses it to an empty + // slice. Indexing that slice must not panic; it must return an error. + cases := []struct { + name string + visual string + editor string + }{ + {name: "VISUAL whitespace", visual: "\t "}, + {name: "EDITOR whitespace", editor: "\t "}, + } + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + t.Setenv("OLLAMA_EDITOR", "") + t.Setenv("VISUAL", tt.visual) + t.Setenv("EDITOR", tt.editor) + _, err := editInExternalEditor("content") + assert.Error(t, err) + }) + } +} + +func TestEditInExternalEditorParsesEditorWithArgs(t *testing.T) { + // A well-formed editor command with arguments must still be parsed so + // its binary is looked up (guards the normal path from regressing). + t.Setenv("OLLAMA_EDITOR", "definitely-not-a-real-editor arg1") + t.Setenv("VISUAL", "") + t.Setenv("EDITOR", "") + _, err := editInExternalEditor("content") + assert.ErrorContains(t, err, "definitely-not-a-real-editor") +} + func TestExtractFileDataWAV(t *testing.T) { dir := t.TempDir() fp := filepath.Join(dir, "sample.wav")