aaif-goose/goose · error

Invalid editor command: unmatched quotes in '{editor_cmd}'

Error message

Invalid editor command: unmatched quotes in '{editor_cmd}'

What it means

split_editor_command switches to shlex splitting when the configured editor command contains quote characters, and shlex::split returns None for unbalanced or mismatched quotes — goose surfaces that as this error. Any stray '"' or "'" in the editor string (an opening quote never closed, or a wrapped value pasted with its quotes into the config) triggers it.

Source

Thrown at crates/goose-cli/src/session/editor.rs:151

    }
}

impl Drop for SymlinkCleanup {
    fn drop(&mut self) {
        let _ = std::fs::remove_file(&self.symlink_path);
    }
}

/// Split an editor command into program and arguments.
///
/// Uses shell-word splitting only when the command contains quotes, so values like
/// `"/Applications/Sublime Text.app/.../subl" -w` work. Unquoted commands are split on
/// whitespace to avoid shlex stripping backslashes from Windows paths like
/// `C:\Windows\System32\notepad.exe`.
fn split_editor_command(editor_cmd: &str) -> Result<Vec<String>> {
    if editor_cmd.contains(['"', '\'']) {
        shlex::split(editor_cmd).ok_or_else(|| {
            anyhow::anyhow!("Invalid editor command: unmatched quotes in '{editor_cmd}'")
        })
    } else {
        Ok(editor_cmd.split_whitespace().map(String::from).collect())
    }
}

/// Launch editor and wait for completion
fn launch_editor(editor_cmd: &str, file_path: &PathBuf) -> Result<()> {
    use std::process::Stdio;

    let parts = split_editor_command(editor_cmd)?;
    if parts.is_empty() {
        return Err(anyhow::anyhow!("Empty editor command"));
    }

    let mut cmd = Command::new(&parts[0]);
    if let Ok(cwd) = std::env::current_dir() {
        cmd.current_dir(cwd);

View on GitHub (pinned to 3810898a74)

Solutions

  1. Inspect the configured editor value (`goose config get editor` or the config file) and fix the quoting: every opening quote must have a matching closing quote.
  2. Store the value without the shell-style wrapper quotes — config files don't need them: `editor = 'subl -w'` in TOML means the value is subl -w.
  3. Quote only the path with spaces: `"/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl" -w`.
  4. After fixing, re-run the command that opens the editor.

Example fix

# before (config value literally contains unmatched quotes)
editor = "/opt/Sublime Text.app/subl -w     # closing quote lost

# after
editor = '"/opt/Sublime Text.app/subl" -w'
Defensive patterns

Strategy: validation

Validate before calling

fn editor_command_is_balanced(cmd: &str) -> bool {
    let mut double = 0i32;
    let mut single = 0i32;
    for ch in cmd.chars() {
        if ch == '"' { double += 1; }
        if ch == '\'' { single += 1; }
    }
    double % 2 == 0 && single % 2 == 0
}
// run before storing/using the editor config; better: shlex::split(cmd).is_some()

Type guard

fn is_valid_editor_command(cmd: &str) -> bool {
    if cmd.contains(['"', '\'']) {
        shlex::split(cmd).is_some()
    } else {
        !cmd.split_whitespace().next().is_none()
    }
}

Try / catch

match launch_editor(&editor_cmd, &path) {
    Err(e) if e.to_string().contains("unmatched quotes") => {
        eprintln!("fix editor config: quote the program path properly, no stray quotes");
    }
    r => r,
}

Prevention

When it happens

Trigger: Editor config like `"/Applications/Sublime Text.app/.../subl -w` (closing quote missing), `vim -c 'set ft=markdown` (unclosed single quote), or a value stored WITH surrounding quotes that were part of a shell example rather than the value.

Common situations: Copy-pasting `EDITOR='subl -w'` including the outer quotes into a TOML/JSON config so the stored value becomes `'subl -w'` with literal quotes; hand-editing config files and leaving a dangling quote; passwords/spaces arguments with asymmetric quoting.

Related errors


AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16). Data as JSON: /api/errors/fe913dcbefd78c54. Report an issue: GitHub.