diff --git a/commands/single_test.go b/commands/single_test.go
index badb8ceb14be109495d675704fbc97ec211dbca8..9121d721c8fd6a7d06e050141a5c950d80147cb4 100644
--- a/commands/single_test.go
+++ b/commands/single_test.go
@@ -83,8 +83,11 @@ func mockingExecutionStack(t *testing.T, executorName string, maxBuilds int, job
 	}
 
 	//ExecutorProvider
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Times(maxBuilds + 1)
+
 	p.On("Create").Return(&e).Times(maxBuilds)
-	p.On("GetFeatures", mock.Anything).Times(maxBuilds)
 	p.On("Acquire", mock.Anything).Return(&common.MockExecutorData{}, nil).Times(maxBuilds)
 	p.On("Release", mock.Anything, mock.Anything).Return(nil).Times(maxBuilds)
 
diff --git a/common/build_test.go b/common/build_test.go
index cf21dd6e373180df0fef34155dbf4b5436691693..4d5734f42f89d5c665f3a73b57e5d9bbec636a1a 100644
--- a/common/build_test.go
+++ b/common/build_test.go
@@ -26,8 +26,11 @@ func TestBuildRun(t *testing.T) {
 	defer p.AssertExpectations(t)
 
 	// Create executor only once
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e).Once()
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// We run everything once
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
@@ -71,8 +74,11 @@ func TestRetryPrepare(t *testing.T) {
 	defer p.AssertExpectations(t)
 
 	// Create executor
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e).Times(3)
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// Prepare plan
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).
@@ -112,8 +118,11 @@ func TestPrepareFailure(t *testing.T) {
 	defer p.AssertExpectations(t)
 
 	// Create executor
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e).Times(3)
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// Prepare plan
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).
@@ -144,8 +153,11 @@ func TestPrepareFailureOnBuildError(t *testing.T) {
 	defer p.AssertExpectations(t)
 
 	// Create executor
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e).Times(1)
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// Prepare plan
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).
@@ -182,8 +194,11 @@ func TestRunFailureRunsAfterScriptAndArtifactsOnFailure(t *testing.T) {
 	defer p.AssertExpectations(t)
 
 	// Create executor
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e).Once()
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// Prepare plan
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
@@ -224,8 +239,11 @@ func TestGetSourcesRunFailure(t *testing.T) {
 	defer p.AssertExpectations(t)
 
 	// Create executor
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e).Once()
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// Prepare plan
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
@@ -264,8 +282,11 @@ func TestArtifactDownloadRunFailure(t *testing.T) {
 	defer p.AssertExpectations(t)
 
 	// Create executor
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e).Once()
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// Prepare plan
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
@@ -306,8 +327,11 @@ func TestArtifactUploadRunFailure(t *testing.T) {
 	defer p.AssertExpectations(t)
 
 	// Create executor
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e).Once()
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// Prepare plan
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
@@ -357,8 +381,11 @@ func TestRestoreCacheRunFailure(t *testing.T) {
 	defer p.AssertExpectations(t)
 
 	// Create executor
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e).Once()
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// Prepare plan
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
@@ -397,8 +424,11 @@ func TestRunWrongAttempts(t *testing.T) {
 	defer p.AssertExpectations(t)
 
 	// Create executor
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e)
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// Prepare plan
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
@@ -433,8 +463,11 @@ func TestRunSuccessOnSecondAttempt(t *testing.T) {
 	p := MockExecutorProvider{}
 
 	// Create executor only once
+	p.On("CanCreate").Return(true).Once()
+	p.On("GetDefaultShell").Return("bash").Once()
+	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
+
 	p.On("Create").Return(&e).Once()
-	p.On("GetFeatures", mock.Anything).Once()
 
 	// We run everything once
 	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
diff --git a/common/executor.go b/common/executor.go
index 1514151ff42f108203174b2416d9f568d3bc2536..2a004c9ec66874c394fed86347a84db4be74146e 100644
--- a/common/executor.go
+++ b/common/executor.go
@@ -2,6 +2,8 @@ package common
 
 import (
 	"context"
+	"errors"
+	"fmt"
 
 	log "github.com/sirupsen/logrus"
 )
@@ -47,7 +49,8 @@ type ExecutorProvider interface {
 	Create() Executor
 	Acquire(config *RunnerConfig) (ExecutorData, error)
 	Release(config *RunnerConfig, data ExecutorData) error
-	GetFeatures(features *FeaturesInfo)
+	GetFeatures(features *FeaturesInfo) error
+	GetDefaultShell() string
 }
 
 type BuildError struct {
@@ -64,9 +67,29 @@ func (b *BuildError) Error() string {
 
 var executors map[string]ExecutorProvider
 
+func validateExecutorProvider(provider ExecutorProvider) error {
+	if provider.GetDefaultShell() == "" {
+		return errors.New("default shell not implemented")
+	}
+
+	if !provider.CanCreate() {
+		return errors.New("cannot create executor")
+	}
+
+	if err := provider.GetFeatures(&FeaturesInfo{}); err != nil {
+		return fmt.Errorf("cannot get features: %v", err)
+	}
+
+	return nil
+}
+
 func RegisterExecutor(executor string, provider ExecutorProvider) {
 	log.Debugln("Registering", executor, "executor...")
 
+	if err := validateExecutorProvider(provider); err != nil {
+		panic("Executor cannot be registered: " + err.Error())
+	}
+
 	if executors == nil {
 		executors = make(map[string]ExecutorProvider)
 	}
diff --git a/common/mock_ExecutorProvider.go b/common/mock_ExecutorProvider.go
index af68e2c4786617fefc3cf267b6a17f34a63afd89..2e5488e351e17a371c77a14f3d8f906ce6694878 100644
--- a/common/mock_ExecutorProvider.go
+++ b/common/mock_ExecutorProvider.go
@@ -64,9 +64,32 @@ func (_m *MockExecutorProvider) Create() Executor {
 	return r0
 }
 
+// GetDefaultShell provides a mock function with given fields:
+func (_m *MockExecutorProvider) GetDefaultShell() string {
+	ret := _m.Called()
+
+	var r0 string
+	if rf, ok := ret.Get(0).(func() string); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(string)
+	}
+
+	return r0
+}
+
 // GetFeatures provides a mock function with given fields: features
-func (_m *MockExecutorProvider) GetFeatures(features *FeaturesInfo) {
-	_m.Called(features)
+func (_m *MockExecutorProvider) GetFeatures(features *FeaturesInfo) error {
+	ret := _m.Called(features)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(*FeaturesInfo) error); ok {
+		r0 = rf(features)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
 }
 
 // Release provides a mock function with given fields: config, data
diff --git a/common/network.go b/common/network.go
index 5ea21b487dc62f318bb063301de21c4145514fa0..4959740859bbdbcd1ab5e6b9299cd902b50fa2ca 100644
--- a/common/network.go
+++ b/common/network.go
@@ -93,6 +93,7 @@ type VersionInfo struct {
 	Platform     string       `json:"platform,omitempty"`
 	Architecture string       `json:"architecture,omitempty"`
 	Executor     string       `json:"executor,omitempty"`
+	Shell        string       `json:"shell,omitempty"`
 	Features     FeaturesInfo `json:"features"`
 }
 
diff --git a/executors/default_executor_provider.go b/executors/default_executor_provider.go
index 716810bb4bf467edd2ad8b80e1c805900cc21fd9..67a4a60cdb40ae747877c7c31e8742d237794815 100644
--- a/executors/default_executor_provider.go
+++ b/executors/default_executor_provider.go
@@ -1,10 +1,15 @@
 package executors
 
-import "gitlab.com/gitlab-org/gitlab-runner/common"
+import (
+	"errors"
+
+	"gitlab.com/gitlab-org/gitlab-runner/common"
+)
 
 type DefaultExecutorProvider struct {
-	Creator         func() common.Executor
-	FeaturesUpdater func(features *common.FeaturesInfo)
+	Creator          func() common.Executor
+	FeaturesUpdater  func(features *common.FeaturesInfo)
+	DefaultShellName string
 }
 
 func (e DefaultExecutorProvider) CanCreate() bool {
@@ -26,8 +31,15 @@ func (e DefaultExecutorProvider) Release(config *common.RunnerConfig, data commo
 	return nil
 }
 
-func (e DefaultExecutorProvider) GetFeatures(features *common.FeaturesInfo) {
-	if e.FeaturesUpdater != nil {
-		e.FeaturesUpdater(features)
+func (e DefaultExecutorProvider) GetFeatures(features *common.FeaturesInfo) error {
+	if e.FeaturesUpdater == nil {
+		return errors.New("cannot evaluate features")
 	}
+
+	e.FeaturesUpdater(features)
+	return nil
+}
+
+func (e DefaultExecutorProvider) GetDefaultShell() string {
+	return e.DefaultShellName
 }
diff --git a/executors/docker/executor_docker_command.go b/executors/docker/executor_docker_command.go
index 955d2bfcd4a59ec42ee9f22257333675e441c27b..c01fbac78f59545613c087f07feda250a4e33ce4 100644
--- a/executors/docker/executor_docker_command.go
+++ b/executors/docker/executor_docker_command.go
@@ -121,7 +121,8 @@ func init() {
 	}
 
 	common.RegisterExecutor("docker", executors.DefaultExecutorProvider{
-		Creator:         creator,
-		FeaturesUpdater: featuresUpdater,
+		Creator:          creator,
+		FeaturesUpdater:  featuresUpdater,
+		DefaultShellName: options.Shell.Shell,
 	})
 }
diff --git a/executors/docker/executor_docker_ssh.go b/executors/docker/executor_docker_ssh.go
index f0e0b3857b88282e58e455c519ed7d5a6ca757ab..740d0a61c4310089df1e6648019ed47e0cf72645 100644
--- a/executors/docker/executor_docker_ssh.go
+++ b/executors/docker/executor_docker_ssh.go
@@ -112,7 +112,8 @@ func init() {
 	}
 
 	common.RegisterExecutor("docker-ssh", executors.DefaultExecutorProvider{
-		Creator:         creator,
-		FeaturesUpdater: featuresUpdater,
+		Creator:          creator,
+		FeaturesUpdater:  featuresUpdater,
+		DefaultShellName: options.Shell.Shell,
 	})
 }
diff --git a/executors/docker/machine/provider.go b/executors/docker/machine/provider.go
index 40ae6fcef4720de6f3e15fd877720833fecee4ce..9111022e805daf2098487e8c58c894a5e83a261d 100644
--- a/executors/docker/machine/provider.go
+++ b/executors/docker/machine/provider.go
@@ -405,8 +405,12 @@ func (m *machineProvider) CanCreate() bool {
 	return m.provider.CanCreate()
 }
 
-func (m *machineProvider) GetFeatures(features *common.FeaturesInfo) {
-	m.provider.GetFeatures(features)
+func (m *machineProvider) GetFeatures(features *common.FeaturesInfo) error {
+	return m.provider.GetFeatures(features)
+}
+
+func (m *machineProvider) GetDefaultShell() string {
+	return m.provider.GetDefaultShell()
 }
 
 func (m *machineProvider) Create() common.Executor {
diff --git a/executors/kubernetes/executor_kubernetes.go b/executors/kubernetes/executor_kubernetes.go
index 7db7157505a18d22610a08178effaca746041b34..35a34e4b8773a0343301bffe11374f89b7a0d98b 100644
--- a/executors/kubernetes/executor_kubernetes.go
+++ b/executors/kubernetes/executor_kubernetes.go
@@ -549,7 +549,8 @@ func featuresFn(features *common.FeaturesInfo) {
 
 func init() {
 	common.RegisterExecutor("kubernetes", executors.DefaultExecutorProvider{
-		Creator:         createFn,
-		FeaturesUpdater: featuresFn,
+		Creator:          createFn,
+		FeaturesUpdater:  featuresFn,
+		DefaultShellName: executorOptions.Shell.Shell,
 	})
 }
diff --git a/executors/parallels/executor_parallels.go b/executors/parallels/executor_parallels.go
index 2b0c0520fe6e93e35b800fa044095f113faf589b..91a37d7887f7cac223673d233bb103ff80834c38 100644
--- a/executors/parallels/executor_parallels.go
+++ b/executors/parallels/executor_parallels.go
@@ -342,7 +342,8 @@ func init() {
 	}
 
 	common.RegisterExecutor("parallels", executors.DefaultExecutorProvider{
-		Creator:         creator,
-		FeaturesUpdater: featuresUpdater,
+		Creator:          creator,
+		FeaturesUpdater:  featuresUpdater,
+		DefaultShellName: options.Shell.Shell,
 	})
 }
diff --git a/executors/shell/executor_shell.go b/executors/shell/executor_shell.go
index 05d14d364543dabab549a573416177196dd00af1..63fdfcad9a7b6f19676ffcd5fa751e3f6e33f206 100644
--- a/executors/shell/executor_shell.go
+++ b/executors/shell/executor_shell.go
@@ -9,12 +9,13 @@ import (
 	"path/filepath"
 
 	"fmt"
+	"time"
+
 	"github.com/kardianos/osext"
 	"github.com/sirupsen/logrus"
 	"gitlab.com/gitlab-org/gitlab-runner/common"
 	"gitlab.com/gitlab-org/gitlab-runner/executors"
 	"gitlab.com/gitlab-org/gitlab-runner/helpers"
-	"time"
 )
 
 type executor struct {
@@ -158,7 +159,8 @@ func init() {
 	}
 
 	common.RegisterExecutor("shell", executors.DefaultExecutorProvider{
-		Creator:         creator,
-		FeaturesUpdater: featuresUpdater,
+		Creator:          creator,
+		FeaturesUpdater:  featuresUpdater,
+		DefaultShellName: options.Shell.Shell,
 	})
 }
diff --git a/executors/ssh/executor_ssh.go b/executors/ssh/executor_ssh.go
index 1ea829c17bd70e5d812e402f68b78c7f05a3cd5b..295b565e03adf8e1e507d77deef5f1bae60b0e5d 100644
--- a/executors/ssh/executor_ssh.go
+++ b/executors/ssh/executor_ssh.go
@@ -88,7 +88,8 @@ func init() {
 	}
 
 	common.RegisterExecutor("ssh", executors.DefaultExecutorProvider{
-		Creator:         creator,
-		FeaturesUpdater: featuresUpdater,
+		Creator:          creator,
+		FeaturesUpdater:  featuresUpdater,
+		DefaultShellName: options.Shell.Shell,
 	})
 }
diff --git a/executors/virtualbox/executor_virtualbox.go b/executors/virtualbox/executor_virtualbox.go
index 49de53736a2fd767e498b07e7e91753c33199455..8936df08f8050d26c21b77d8653bd66e387d5baf 100644
--- a/executors/virtualbox/executor_virtualbox.go
+++ b/executors/virtualbox/executor_virtualbox.go
@@ -3,11 +3,12 @@ package virtualbox
 import (
 	"errors"
 	"fmt"
+	"time"
+
 	"gitlab.com/gitlab-org/gitlab-runner/common"
 	"gitlab.com/gitlab-org/gitlab-runner/executors"
 	"gitlab.com/gitlab-org/gitlab-runner/helpers/ssh"
 	vbox "gitlab.com/gitlab-org/gitlab-runner/helpers/virtualbox"
-	"time"
 )
 
 type executor struct {
@@ -321,7 +322,8 @@ func init() {
 	}
 
 	common.RegisterExecutor("virtualbox", executors.DefaultExecutorProvider{
-		Creator:         creator,
-		FeaturesUpdater: featuresUpdater,
+		Creator:          creator,
+		FeaturesUpdater:  featuresUpdater,
+		DefaultShellName: options.Shell.Shell,
 	})
 }
diff --git a/network/gitlab.go b/network/gitlab.go
index 46c60450f32a47e7a5a703f3d5b00898c3323c6a..f954f1674c21df33120d3638b548dee0d1fa8212 100644
--- a/network/gitlab.go
+++ b/network/gitlab.go
@@ -132,13 +132,18 @@ func (n *GitLabClient) getRunnerVersion(config common.RunnerConfig) common.Versi
 		Platform:     runtime.GOOS,
 		Architecture: runtime.GOARCH,
 		Executor:     config.Executor,
+		Shell:        config.Shell,
 	}
 
 	if executor := common.GetExecutor(config.Executor); executor != nil {
 		executor.GetFeatures(&info.Features)
+
+		if info.Shell == "" {
+			info.Shell = executor.GetDefaultShell()
+		}
 	}
 
-	if shell := common.GetShell(config.Shell); shell != nil {
+	if shell := common.GetShell(info.Shell); shell != nil {
 		shell.GetFeatures(&info.Features)
 	}
 
diff --git a/network/gitlab_test.go b/network/gitlab_test.go
index 7366951033d6b3ecbeac200fd28557d5507f8707..7f5c47de68fbfff0a078afb379905528914fb4eb 100644
--- a/network/gitlab_test.go
+++ b/network/gitlab_test.go
@@ -13,6 +13,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/require"
 
 	. "gitlab.com/gitlab-org/gitlab-runner/common"
@@ -982,3 +983,56 @@ func TestArtifactsDownload(t *testing.T) {
 	state = c.DownloadArtifacts(fileNotFoundTokenCredentials, artifactsFileName)
 	assert.Equal(t, DownloadNotFound, state, "Artifacts should be bit downloaded if it's not found")
 }
+
+func TestRunnerVersion(t *testing.T) {
+	c := NewGitLabClient()
+	info := c.getRunnerVersion(RunnerConfig{
+		RunnerSettings: RunnerSettings{
+			Executor: "my-executor",
+			Shell:    "my-shell",
+		},
+	})
+
+	assert.NotEmpty(t, info.Name)
+	assert.NotEmpty(t, info.Version)
+	assert.NotEmpty(t, info.Revision)
+	assert.NotEmpty(t, info.Platform)
+	assert.NotEmpty(t, info.Architecture)
+	assert.Equal(t, "my-executor", info.Executor)
+	assert.Equal(t, "my-shell", info.Shell)
+}
+
+func TestRunnerVersionToGetExecutorAndShellFeaturesWithTheDefaultShell(t *testing.T) {
+	executorProvider := MockExecutorProvider{}
+	defer executorProvider.AssertExpectations(t)
+	executorProvider.On("GetDefaultShell").Return("my-default-executor-shell").Twice()
+	executorProvider.On("CanCreate").Return(true).Once()
+	executorProvider.On("GetFeatures", mock.Anything).Return(nil).Run(func(args mock.Arguments) {
+		features := args[0].(*FeaturesInfo)
+		features.Shared = true
+	})
+	RegisterExecutor("my-test-executor", &executorProvider)
+
+	shell := MockShell{}
+	defer shell.AssertExpectations(t)
+	shell.On("GetName").Return("my-default-executor-shell")
+	shell.On("GetFeatures", mock.Anything).Return(nil).Run(func(args mock.Arguments) {
+		features := args[0].(*FeaturesInfo)
+		features.Variables = true
+	})
+	RegisterShell(&shell)
+
+	c := NewGitLabClient()
+	info := c.getRunnerVersion(RunnerConfig{
+		RunnerSettings: RunnerSettings{
+			Executor: "my-test-executor",
+			Shell:    "",
+		},
+	})
+
+	assert.Equal(t, "my-test-executor", info.Executor)
+	assert.Equal(t, "my-default-executor-shell", info.Shell)
+	assert.False(t, info.Features.Artifacts, "dry-run that this is not enabled")
+	assert.True(t, info.Features.Shared, "feature is enabled by executor")
+	assert.True(t, info.Features.Variables, "feature is enabled by shell")
+}