{"record":{"id":"6bf8f181a54677a9","repo":"sipeed/picoclaw","slug":"local-command-q-is-a-directory","errorCode":null,"errorMessage":"local command %q is a directory","messagePattern":"local command %q is a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/picoclaw/internal/mcp/helpers.go","lineNumber":322,"sourceCode":"\t}\n\treturn path\n}\n\nfunc validateLocalCommandPath(command string) error {\n\tif !isLocalCommandPath(command) {\n\t\treturn nil\n\t}\n\n\tpath := expandHomePath(command)\n\tinfo, err := os.Stat(path)\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\treturn fmt.Errorf(\"local command %q does not exist\", command)\n\t\t}\n\t\treturn fmt.Errorf(\"failed to stat local command %q: %w\", command, err)\n\t}\n\tif info.IsDir() {\n\t\treturn fmt.Errorf(\"local command %q is a directory\", command)\n\t}\n\tif runtime.GOOS != \"windows\" && info.Mode()&0o111 == 0 {\n\t\treturn fmt.Errorf(\"local command %q is not executable\", command)\n\t}\n\treturn nil\n}\n\nfunc defaultServerProbe(\n\tctx context.Context,\n\tname string,\n\tserver config.MCPServerConfig,\n\tworkspacePath string,\n) (probeResult, error) {\n\tmgr := picomcp.NewManager()\n\tdefer func() { _ = mgr.Close() }()\n\n\tserver.Enabled = true\n\tmcpCfg := config.MCPConfig{","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/mcp/helpers.go#L304-L340","documentation":"Thrown by validateLocalCommandPath in picoclaw's MCP helpers. When a configured local (stdio) MCP server command looks like a path (absolute, ./, ../, or containing a path separator), picoclaw expands ~ and stats it; this error means the path exists but is a directory. picoclaw needs an executable file to spawn as the server process, so it rejects a folder before any connection attempt. Bare command names like npx are never stat-ed and cannot produce this error.","triggerScenarios":"An MCP server entry whose command is a directory, e.g. command: /usr/local/bin or command: ~/tools/mcp-server/; isLocalCommandPath returns true (path-like), os.Stat succeeds, and info.IsDir() is true.","commonSituations":"Pointing command at an install or bin directory (npm global bin dir, ~/.local/bin) instead of the binary inside it; pasting the folder path of a cloned/downloaded server; scaffolding a directory whose name collides with a relative command used from inside it.","solutions":["Point command at the executable file itself (e.g. /usr/local/bin/weather-server, not /usr/local/bin) via picoclaw mcp remove + picoclaw mcp add or by editing the config","Verify first: ls -ld <command> — a leading d in the mode column means it is a directory","For package-run servers prefer wrapper commands such as npx -y <package> or uvx <package>, which need no path","If the server is a script inside a folder, reference the script file plus its args, not the folder"],"exampleFix":"# before (config tools.mcp.servers.weather)\ncommand: /home/dev/.local/share/mcp-servers\n\n# after\ncommand: /home/dev/.local/share/mcp-servers/weather-server","handlingStrategy":"validation","validationCode":"func validateCommandPath(command string) error {\n  if command == \"\" || !strings.ContainsRune(command, os.PathSeparator) {\n    return nil // bare command names are not stat-ed by picoclaw either\n  }\n  path := command\n  if strings.HasPrefix(path, \"~\") {\n    home, _ := os.UserHomeDir()\n    path = filepath.Join(home, strings.TrimPrefix(path, \"~\"))\n  }\n  info, err := os.Stat(path)\n  if err != nil {\n    return fmt.Errorf(\"command %s does not resolve: %w\", command, err)\n  }\n  if info.IsDir() {\n    return fmt.Errorf(\"command %s is a directory; point it at the executable file\", command)\n  }\n  return nil\n}\n\n// call before saving the server config or running picoclaw mcp add","typeGuard":"func isCommandDirError(err error) bool {\n  return err != nil && strings.Contains(err.Error(), \"local command\") && strings.Contains(err.Error(), \"is a directory\")\n}","tryCatchPattern":"if err := runMCPAdd(cfg); err != nil {\n  if isCommandDirError(err) {\n    // prompt the user for the full executable path instead of the folder, then retry\n  }\n  return err\n}","preventionTips":["Always configure command as a file path, never a folder; verify with ls -ld before saving","Prefer absolute paths so behavior does not depend on the working directory","For package-run servers use npx/uvx wrappers, which bypass path validation"],"tags":["mcp","config","filesystem","validation","cli"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}