semaphoreui/semaphore · error

executor provider is not initialised (check runner executor…

Error message

executor provider is not initialised (check runner executor config)

What it means

newExecutor was invoked with a nil ExecutorProvider, so no executor can be constructed for the job. The provider is created from the runner executor config; nil means the factory never produced one (e.g. unknown executor type or config omitted), so the guard fails fast with an actionable message instead of panicking on a nil pointer.

Solutions

  1. Check runner startup logs for an error from newExecutorProvider and fix the executor configuration.
  2. Set a valid executor.type in the runner config and restart the runner.
  3. Ensure the runner fails fast at startup (or re-initializes the provider) instead of reaching the job loop with a nil provider.

Example fix

// before
provider, _ := newExecutorProvider(cfg) // error ignored, provider nil
// after
provider, err := newExecutorProvider(cfg)
if err != nil {
    return fmt.Errorf("init executor provider: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

if pool.Provider() == nil {
    return errors.New("executor provider not initialized; check runner executor config before accepting jobs")
}

Type guard

func providerReady(p tasks.ExecutorProvider) bool { return p != nil }

Try / catch

ex, err := newExecutor(jobData, keys, provider)
if err != nil {
    if strings.Contains(err.Error(), "not initialised") {
        log.Error("executor provider missing; halting job processing for reconfiguration")
        return
    }
    return err
}

Prevention

When it happens

Trigger: checkNewJobs picks up a job and calls newExecutor while the JobPool's provider is nil — typically because newExecutorProvider returned (nil, err) at startup (unknown executor type, docker provider init failure) or the config tree was nil and nothing defaulted.

Common situations: Runner started with a misconfigured/invalid executor section so the provider was never initialized; provider construction failed at startup but the job loop kept running.

Understand the failure class

Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/a1543340bbaa78b4. Report an issue: GitHub.

Appendix: source

Thrown at services/runners/executor_factory.go:61

// 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(
	jobData JobData,
	accessKeys map[int]db.AccessKey,
	provider tasks.ExecutorProvider,
) (tasks.Executor, error) {
	if provider == nil {
		return nil, fmt.Errorf("executor provider is not initialised (check runner executor config)")
	}

	hydrateJobAccessKeys(&jobData, accessKeys)

	return provider.NewExecutor(
		jobData.Task,
		jobData.Template,
		jobData.Inventory,
		jobData.Repository,
		jobData.Environment,
		jobData.JWT,
	)
}

// hydrateJobAccessKeys decrypts/wires the access keys the server sent us into the
// per-task data. Lives in the factory (not the Provider) so the behaviour is
// identical across strategies — every executor sees the same shape of JobData.
func hydrateJobAccessKeys(jobData *JobData, accessKeys map[int]db.AccessKey) {

View on GitHub (pinned to 1774ccb71a)