semaphoreui/semaphore · error

encountered error while unregistering runner; server…

Error message

encountered error while unregistering runner; server returned code %d

What it means

The DELETE request to the server's internal runners endpoint returned an HTTP status >= 400 (and not 404, which is tolerated since the runner is effectively already gone). Unregister treats any other client/server error status as a failed unregistration and surfaces the status code.

Solutions

  1. Check the reported status code: 401/403 means the stored token is invalid — re-register or remove the runner manually in the server UI.
  2. Verify the server is healthy and reachable (status 5xx usually indicates server/proxy issues).
  3. Retry the unregister after the server recovers; 404 is already treated as success.
  4. If the runner was deleted server-side already, ignore the error or clear the local token.
Defensive patterns

Strategy: retry

Validate before calling

// no local pre-check possible; verify server health before unregister
if err := pingServer(util.Config.WebHost); err != nil {
    return fmt.Errorf("server unreachable, defer unregister: %w", err)
}

Try / catch

if err := pool.Unregister(); err != nil {
    var statusErr interface{ StatusCode() int }
    if strings.Contains(err.Error(), "server returned code 5") {
        time.Sleep(10 * time.Second)
        return pool.Unregister() // retry once after server recovers
    }
    return err
}

Prevention

When it happens

Trigger: Unregister() sends DELETE to WebHost + /api/internal/runners and the response status is e.g. 401/403 (bad token), 500 (server-side failure), or 502/503 (proxy downtime).

Common situations: Stale or invalid runner token rejected by the server; server behind a reverse proxy returning 502 during maintenance; server bug while deleting the runner record.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

Thrown at services/runners/job_pool.go:243

	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
	}
	defer resp.Body.Close() //nolint:errcheck

	if resp.StatusCode >= 400 && resp.StatusCode != 404 {
		err = fmt.Errorf("encountered error while unregistering runner; server returned code %d", resp.StatusCode)
		return
	}

	log.WithFields(log.Fields{
		"context":     "unregistration",
		"status_code": resp.StatusCode,
	}).Debug("Runner unregistered on the server")

	if util.Config.Runner.TokenFile != "" {
		err = os.Remove(util.Config.Runner.TokenFile)
	}

	return
}

// runnerProgressInterval is how often the runner reports progress. It is not
// configurable: the report is also the heartbeat the server uses to decide a
// runner is still alive.

View on GitHub (pinned to 1774ccb71a)