semaphoreui/semaphore · error
unknown runner executor type
Error message
unknown runner executor type %q
What it means
The runner's executor factory did not recognize the configured executor type. newExecutorProvider switches on the executor type resolved from the runner config (defaulting to "local" when empty) and the default branch means the value is neither local, docker, nor any supported provider. No executor provider is created and the runner cannot execute jobs.
Solutions
- Open the runner config and set executor.type to a supported value (e.g. "local" or "docker").
- Check for typos and trailing whitespace in the executor type value.
- Confirm the binary version supports the configured executor type; upgrade or downgrade accordingly.
- Remove the executor section entirely to fall back to the "local" default.
Example fix
// before (config) executor: type: dockr // after executor: type: docker
Defensive patterns
Strategy: validation
Validate before calling
validTypes := map[string]bool{"local": true, "docker": true}
execType := cfg.Executor.Type
if execType == "" { execType = "local" }
if !validTypes[execType] {
return fmt.Errorf("executor type %q not supported by this runner build", execType)
} Try / catch
pool, err := runners.NewJobPool(cfg)
if err != nil {
if strings.Contains(err.Error(), "unknown runner executor type") {
log.Fatalf("fix executor.type in runner config: %v", err)
}
return err
} Prevention
- Copy executor type values only from the documentation matching your binary version.
- Lint runner configs against a schema before startup.
- Prefer omitting executor.type to get the "local" default.
When it happens
Trigger: newExecutorProvider is called with a config whose executor.type (after resolveExecutorType defaulting) is an unrecognized string, e.g. a typo like "dockr" or a type not compiled into this build.
Common situations: Typo in the runner's config.yml executor type; copying config from documentation of a newer/older version with extra executor types; environment-specific config templates with invalid values.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- executor provider is not initialised (check runner executor…
- OIDC sign-in failed: invalid redirect URL.
- Empty token
- docker executor is only available in the proprietary build
- k8s executor is only available in the proprietary build
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/01cb25fd1cddf126.
Report an issue: GitHub.
Appendix: source
Thrown at services/runners/executor_factory.go:37
// else (JobPool, executor lifecycle, access-key hydration) needs to change.
func newExecutorProvider(executorCfg *util.ExecutorConfig, keyInstaller db_lib.AccessKeyInstaller) (tasks.ExecutorProvider, error) {
switch resolveExecutorType(executorCfg) {
case util.ExecutorTypeLocal:
return tasks.NewLocalExecutorProvider(keyInstaller), nil
case util.ExecutorTypeKubernetes:
k8sCfg := util.RunnerK8sConfig{}
if executorCfg != nil {
k8sCfg = executorCfg.K8s
}
return k8s.NewProvider(k8sCfg)
case util.ExecutorTypeDocker:
dockerCfg := util.RunnerDockerConfig{}
if executorCfg != nil {
dockerCfg = executorCfg.Docker
}
return docker.NewProvider(dockerCfg)
default:
return nil, fmt.Errorf("unknown runner executor type %q", resolveExecutorType(executorCfg))
}
}
// resolveExecutorType returns the executor type from config, defaulting to "local"
// when the field is missing or empty. Defaulting in one place keeps the rest of the
// runner code free of nil-checks against the config tree.
func resolveExecutorType(executorCfg *util.ExecutorConfig) util.ExecutorType {
if executorCfg == nil || executorCfg.Type == "" {
return util.ExecutorTypeLocal
}
return executorCfg.Type
}
// newExecutor wires per-task data through the Provider. Access keys are hydrated
// here (not inside each Provider) so the behaviour is identical regardless of
// strategy: ansible vault passwords, SSH keys, inventory keys, and the inventory
// repo SSH key all land on the JobData before the Executor is built.
func newExecutor(View on GitHub (pinned to 1774ccb71a)