{"record":{"id":"074469c6779d61c0","repo":"aaif-goose/goose","slug":"no-command-provided-in-extension-string","errorCode":null,"errorMessage":"No command provided in extension string","messagePattern":"No command provided in extension string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-cli/src/session/mod.rs","lineNumber":355,"sourceCode":"    }\n\n    /// Parse a stdio extension command string into an ExtensionConfig\n    /// Format: \"ENV1=val1 ENV2=val2 command args...\"\n    pub fn parse_stdio_extension(extension_command: &str) -> Result<ExtensionConfig> {\n        let mut parts = goose::utils::split_command_args(extension_command)?;\n        let mut envs = HashMap::new();\n\n        while let Some(part) = parts.first() {\n            if !part.contains('=') {\n                break;\n            }\n            let env_part = parts.remove(0);\n            let (key, value) = env_part.split_once('=').unwrap();\n            envs.insert(key.to_string(), value.to_string());\n        }\n\n        if parts.is_empty() {\n            return Err(anyhow::anyhow!(\"No command provided in extension string\"));\n        }\n\n        let cmd = parts.remove(0);\n        let name = std::path::Path::new(&cmd)\n            .file_name()\n            .and_then(|f| f.to_str())\n            .unwrap_or(\"unnamed\")\n            .to_string();\n\n        Ok(ExtensionConfig::Stdio {\n            name,\n            cmd,\n            args: parts,\n            envs: Envs::new(envs),\n            env_keys: Vec::new(),\n            description: goose::config::DEFAULT_EXTENSION_DESCRIPTION.to_string(),\n            timeout: Some(goose::config::DEFAULT_EXTENSION_TIMEOUT),\n            cwd: None,","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-cli/src/session/mod.rs#L337-L373","documentation":"Stdio extension strings have the shape 'KEY=VALUE cmd args...': leading tokens containing '=' are consumed as environment variables, and the next token becomes the command. parse_stdio_extension fails when, after consuming env assignments, no token remains to serve as the command.","triggerScenarios":"goose add-extension / --with-extension / config strings such as 'FOO=bar' (env assignments only), an empty command after word splitting, or an unset shell variable that expanded to nothing.","commonSituations":"Quoting mistakes where only the env part survives; copy-pasted extension strings missing the binary; passing \"API_KEY=$TOKEN\" when $TOKEN is unset.","solutions":["Append the server command after the env assignments: 'API_KEY=x node server.js'","Echo the extension string before passing it to goose to verify it expands as expected","Check for stray quotes or empty shell variables"],"exampleFix":"# before\ngoose session add-extension \"API_KEY=$MY_TOKEN\"\n# after\ngoose session add-extension \"API_KEY=$MY_TOKEN node /abs/path/mcp-server/build/index.js\"","handlingStrategy":"validation","validationCode":"fn has_command(ext: &str) -> bool {\n    ext.split_whitespace().any(|p| !p.contains('='))\n}\n\nassert!(has_command(&extension_command), \"extension string needs a command after KEY=VALUE parts\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pair env assignments with the command token: 'KEY=VALUE cmd args'","Echo the final extension string after shell expansion before passing it to goose","Quote the whole extension string so word splitting happens inside goose only"],"tags":["extension","parsing","config","cli"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}