{"record":{"id":"353324040585dc28","repo":"semaphoreui/semaphore","slug":"encountered-error-while-unregistering-runner-serv","errorCode":null,"errorMessage":"encountered error while unregistering runner; server returned code %d","messagePattern":"encountered error while unregistering runner; server returned code (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/runners/job_pool.go","lineNumber":243,"sourceCode":"\n\treq, err := http.NewRequest(\"DELETE\", url, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tlog.WithFields(log.Fields{\n\t\t\"context\": \"unregistration\",\n\t\t\"url\":     url,\n\t}).Debug(\"Sending unregistration request to the server\")\n\n\tresp, err := p.client.Do(req)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer resp.Body.Close() //nolint:errcheck\n\n\tif resp.StatusCode >= 400 && resp.StatusCode != 404 {\n\t\terr = fmt.Errorf(\"encountered error while unregistering runner; server returned code %d\", resp.StatusCode)\n\t\treturn\n\t}\n\n\tlog.WithFields(log.Fields{\n\t\t\"context\":     \"unregistration\",\n\t\t\"status_code\": resp.StatusCode,\n\t}).Debug(\"Runner unregistered on the server\")\n\n\tif util.Config.Runner.TokenFile != \"\" {\n\t\terr = os.Remove(util.Config.Runner.TokenFile)\n\t}\n\n\treturn\n}\n\n// runnerProgressInterval is how often the runner reports progress. It is not\n// configurable: the report is also the heartbeat the server uses to decide a\n// runner is still alive.","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/services/runners/job_pool.go#L225-L261","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Check the reported status code: 401/403 means the stored token is invalid — re-register or remove the runner manually in the server UI.","Verify the server is healthy and reachable (status 5xx usually indicates server/proxy issues).","Retry the unregister after the server recovers; 404 is already treated as success.","If the runner was deleted server-side already, ignore the error or clear the local token."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// no local pre-check possible; verify server health before unregister\nif err := pingServer(util.Config.WebHost); err != nil {\n    return fmt.Errorf(\"server unreachable, defer unregister: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := pool.Unregister(); err != nil {\n    var statusErr interface{ StatusCode() int }\n    if strings.Contains(err.Error(), \"server returned code 5\") {\n        time.Sleep(10 * time.Second)\n        return pool.Unregister() // retry once after server recovers\n    }\n    return err\n}","preventionTips":["Handle 404 as success (already unregistered).","Retry unregister on 5xx; don't retry 4xx auth failures.","Monitor runner tokens for revocation to avoid 401/403."],"tags":["runner","unregister","http","api"],"backgroundTag":"http-error-response","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}