t8y2/dbx · error

etcd v2 probe against %s failed: HTTP %d %s

Error message

etcd v2 probe against %s failed: HTTP %d %s

What it means

This is the fallback error from probeV2: the v2 API endpoint returned an HTTP status other than 200, 403, 404, or 401. The message includes the status code and up to 4KB of the response body so the underlying server-side failure is visible. It wraps whatever non-happy-path HTTP response the probe got.

Source

Thrown at agents/drivers/etcd2-go/client.go:272

// PERMISSION_DENIED handling for restricted users.
func (c *authenticatedClient) probeV2(ctx context.Context) (map[string]any, error) {
	response, err := c.request(ctx, http.MethodGet, "/v2/members", "", nil)
	if err != nil {
		return nil, err
	}
	defer drainClose(response.Body)
	switch response.StatusCode {
	case http.StatusOK:
		return map[string]any{"ok": true, "endpoint": c.endpoint}, nil
	case http.StatusForbidden:
		return map[string]any{"ok": true, "endpoint": c.endpoint, "limited": true}, nil
	case http.StatusNotFound:
		return nil, fmt.Errorf("ETCD_V2_API_DISABLED: %s does not expose the etcd v2 API (removed in etcd 3.6+)", c.endpoint)
	case http.StatusUnauthorized:
		return nil, fmt.Errorf("ETCD_UNAUTHENTICATED: authentication failed against %s", c.endpoint)
	default:
		body, _ := io.ReadAll(io.LimitReader(response.Body, 4096))
		return nil, fmt.Errorf("etcd v2 probe against %s failed: HTTP %d %s", c.endpoint, response.StatusCode, strings.TrimSpace(string(body)))
	}
}

// do performs a v2 API request and returns the body. Non-2xx responses are
// converted into etcdError values carrying the server's errorCode/message.
func (c *authenticatedClient) do(ctx context.Context, method, path, body string, header map[string]string) ([]byte, *http.Response, error) {
	response, err := c.request(ctx, method, path, body, header)
	if err != nil {
		return nil, response, err
	}
	payload, readErr := io.ReadAll(response.Body)
	_ = response.Body.Close()
	if response.StatusCode < 200 || response.StatusCode >= 300 {
		return nil, response, errorFromResponse(response.StatusCode, payload)
	}
	if readErr != nil {
		return nil, response, readErr
	}

View on GitHub (pinned to c0390bff16)

Solutions

  1. Read the HTTP status and body in the message to identify the server-side cause.
  2. Check cluster health: etcdctl endpoint health / endpoint status; look for 'no leader' or low quorum.
  3. If a proxy sits in front of etcd, bypass it or fix its routing/health checks for port 2379.
  4. Retry after the cluster recovers; this condition is often transient.
Defensive patterns

Strategy: retry

Validate before calling

resp, err := http.Get(endpoint + "/health")
if err != nil || resp.StatusCode != http.StatusOK { return fmt.Errorf("endpoint not healthy, aborting probe") }

Try / catch

err := probeClient(ctx, cfg)
if err != nil && strings.Contains(err.Error(), "failed: HTTP 5") {
    return retryWithBackoff(ctx, 3, probeClient, cfg) // transient 5xx
}

Prevention

When it happens

Trigger: probeClient receives statuses like 500 (etcd panics/overloaded), 503 (no leader / cluster unavailable), 400, or a proxy's HTML error page on the v2 probe request.

Common situations: etcd cluster without a leader (post-failure election), reverse proxy or load balancer returning 502/503, etcd still starting up, TLS-terminating proxy misrouting the request.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/59907bc837f22d8d. Report an issue: GitHub.