{"record":{"id":"fe913dcbefd78c54","repo":"aaif-goose/goose","slug":"invalid-editor-command-unmatched-quotes-in-edit","errorCode":null,"errorMessage":"Invalid editor command: unmatched quotes in '{editor_cmd}'","messagePattern":"Invalid editor command: unmatched quotes in '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-cli/src/session/editor.rs","lineNumber":151,"sourceCode":"    }\n}\n\nimpl Drop for SymlinkCleanup {\n    fn drop(&mut self) {\n        let _ = std::fs::remove_file(&self.symlink_path);\n    }\n}\n\n/// Split an editor command into program and arguments.\n///\n/// Uses shell-word splitting only when the command contains quotes, so values like\n/// `\"/Applications/Sublime Text.app/.../subl\" -w` work. Unquoted commands are split on\n/// whitespace to avoid shlex stripping backslashes from Windows paths like\n/// `C:\\Windows\\System32\\notepad.exe`.\nfn split_editor_command(editor_cmd: &str) -> Result<Vec<String>> {\n    if editor_cmd.contains(['\"', '\\'']) {\n        shlex::split(editor_cmd).ok_or_else(|| {\n            anyhow::anyhow!(\"Invalid editor command: unmatched quotes in '{editor_cmd}'\")\n        })\n    } else {\n        Ok(editor_cmd.split_whitespace().map(String::from).collect())\n    }\n}\n\n/// Launch editor and wait for completion\nfn launch_editor(editor_cmd: &str, file_path: &PathBuf) -> Result<()> {\n    use std::process::Stdio;\n\n    let parts = split_editor_command(editor_cmd)?;\n    if parts.is_empty() {\n        return Err(anyhow::anyhow!(\"Empty editor command\"));\n    }\n\n    let mut cmd = Command::new(&parts[0]);\n    if let Ok(cwd) = std::env::current_dir() {\n        cmd.current_dir(cwd);","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-cli/src/session/editor.rs#L133-L169","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","Quote only the path with spaces: `\"/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl\" -w`.","After fixing, re-run the command that opens the editor."],"exampleFix":"# before (config value literally contains unmatched quotes)\neditor = \"/opt/Sublime Text.app/subl -w     # closing quote lost\n\n# after\neditor = '\"/opt/Sublime Text.app/subl\" -w'","handlingStrategy":"validation","validationCode":"fn editor_command_is_balanced(cmd: &str) -> bool {\n    let mut double = 0i32;\n    let mut single = 0i32;\n    for ch in cmd.chars() {\n        if ch == '\"' { double += 1; }\n        if ch == '\\'' { single += 1; }\n    }\n    double % 2 == 0 && single % 2 == 0\n}\n// run before storing/using the editor config; better: shlex::split(cmd).is_some()","typeGuard":"fn is_valid_editor_command(cmd: &str) -> bool {\n    if cmd.contains(['\"', '\\'']) {\n        shlex::split(cmd).is_some()\n    } else {\n        !cmd.split_whitespace().next().is_none()\n    }\n}","tryCatchPattern":"match launch_editor(&editor_cmd, &path) {\n    Err(e) if e.to_string().contains(\"unmatched quotes\") => {\n        eprintln!(\"fix editor config: quote the program path properly, no stray quotes\");\n    }\n    r => r,\n}","preventionTips":["In config files, store the editor value without shell wrapper quotes.","Quote only path segments containing spaces, and always close what you open.","Validate the editor string with shlex::split when saving the config."],"tags":["editor","configuration","shell-quoting","cli"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}