semaphoreui/semaphore · error
webhook returned incorrect status
Error message
webhook returned incorrect status
What it means
callRunnerWebhook delivers a task notification to a runner's webhook endpoint and accepts only HTTP 200 and 204 as success. Any other status code from the runner webhook yields this error, which callers (Run, finalizeRemoteTaskLocked) surface while finalizing the remote task.
Solutions
- Check the runner's logs for the incoming webhook request and its response status
- Verify the webhook URL configured for the runner is correct and reachable from the server
- Ensure any proxy/load-balancer in front of the runner forwards the request unchanged and passes 2xx through
- Confirm runner and server versions are compatible (same webhook API contract)
- Retry the task if the runner was temporarily unavailable
Defensive patterns
Strategy: retry
Validate before calling
// pre-check endpoint health before webhook delivery
resp, err := http.Head(runnerWebhookURL)
if err != nil || resp.StatusCode >= 500 { /* runner unhealthy, defer */ } Type guard
func webhookAccepted(code int) bool { return code == 200 || code == 204 } Try / catch
if err := callRunnerWebhook(...); err != nil {
if strings.Contains(err.Error(), "webhook returned incorrect status") {
// retry with backoff or re-queue the task
}
} Prevention
- Monitor runner webhook endpoints for 4xx/5xx responses
- Keep runner and server versions aligned on the webhook contract
- Bypass or correctly configure proxies in front of runners
- Accept that only 200/204 are valid success codes when implementing runners
When it happens
Trigger: The runner's webhook URL responds with 4xx/5xx (auth failure at the runner, runner restarting, wrong webhook route, proxy interference) or any non-200/204 code such as 301/302 redirects that the HTTP client did not follow.
Common situations: Runner behind a misconfigured reverse proxy returning 502/503; webhook endpoint requires auth that the server does not provide; runner version changed its API and now returns a different success status; load balancer health checks intercepting the URL.
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
- Unauthorized
- internal error
- Account linking must be initiated with a POST request.
- You must be signed in to link an external account.
- OIDC sign-in failed: state cookie is missing. Try signing…
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/389fcd41dc1f2f05.
Report an issue: GitHub.
Appendix: source
Thrown at services/tasks/RemoteJob.go:79
req, err = http.NewRequest("POST", runner.Webhook, bytes.NewBuffer(jsonBytes))
if err != nil {
return
}
req.Header.Set("Content-Type", "application/json")
var resp *http.Response
resp, err = client.Do(req)
if err != nil {
return
}
if resp != nil {
defer resp.Body.Close() //nolint:errcheck
}
if resp.StatusCode != 200 && resp.StatusCode != 204 {
err = fmt.Errorf("webhook returned incorrect status")
return
}
return
}
func shuffleRunners(rs []db.Runner) []db.Runner {
if len(rs) < 2 {
return rs
}
// Work on a copy so that if randomness fails, we can safely return the original order.
shuffled := make([]db.Runner, len(rs))
copy(shuffled, rs)
// Fisher–Yates shuffle using crypto/rand: for each i, pick j in [0, i].
for i := len(shuffled) - 1; i > 0; i-- {
max := big.NewInt(int64(i + 1))View on GitHub (pinned to 1774ccb71a)