semaphoreui/semaphore · error

runner is not registered

Error message

runner is not registered

What it means

Unregister was called but the runner has no registration token in config (util.Config.Runner.Token is empty), meaning it was never registered (or the token was removed). Unregistering without a token is meaningless, so it fails immediately without making any HTTP request.

Solutions

  1. Only call Unregister after a successful Register; guard the shutdown path on the runner's registered state.
  2. If the runner should be registered, run Register first to obtain and persist a token.
  3. If unregistering manually, ensure the config file contains the runner's token.

Example fix

// before
pool.Unregister()
// after
if util.Config.Runner.Token != "" {
    if err := pool.Unregister(); err != nil { log.Error(err) }
}
Defensive patterns

Strategy: validation

Validate before calling

if util.Config.Runner.Token == "" {
    log.Info("runner not registered; skipping unregister")
    return nil
}
return pool.Unregister()

Type guard

func isRegistered() bool { return util.Config.Runner.Token != "" }

Try / catch

if err := pool.Unregister(); err != nil {
    if strings.Contains(err.Error(), "not registered") {
        return nil // nothing to do
    }
    return err
}

Prevention

When it happens

Trigger: Calling JobPool.Unregister() (via unregisterRunner) when the runner config has no Runner.Token — e.g. unregistering a runner that never completed Register, or after the token was blanked in config.

Common situations: Shutdown/cleanup path runs even when startup registration failed; operator runs the unregister command against a fresh runner that was never registered; config file regenerated without the token.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at services/runners/job_pool.go:221

	return false
}

func (p *JobPool) Register(configFilePath *string) (err error) {

	ok := p.tryRegisterRunner(configFilePath)

	if !ok {
		err = fmt.Errorf("runner registration failed")
		return
	}

	return
}

func (p *JobPool) Unregister() (err error) {

	if util.Config.Runner.Token == "" {
		return fmt.Errorf("runner is not registered")
	}

	url := util.Config.WebHost + "/api/internal/runners"

	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return
	}

	log.WithFields(log.Fields{
		"context": "unregistration",
		"url":     url,
	}).Debug("Sending unregistration request to the server")

	resp, err := p.client.Do(req)
	if err != nil {
		return
	}

View on GitHub (pinned to 1774ccb71a)