t8y2/dbx · error
etcd connection error: %w
Error message
etcd connection error: %w
What it means
requestAt wraps any transport-level failure of the HTTP request (DNS failure, connection refused/reset, TLS handshake error, timeout) into the sentinel-prefixed error 'etcd connection error: %w'. It is deliberately gRPC-style so host-side transient-error handling can keep classifying it as a connection problem. No HTTP response was received at all.
Source
Thrown at agents/drivers/etcd2-go/client.go:320
reader = strings.NewReader(body)
}
req, err := http.NewRequestWithContext(ctx, method, strings.TrimSuffix(endpoint, "/")+path, reader)
if err != nil {
return nil, err
}
if c.username != "" {
req.SetBasicAuth(c.username, c.password)
}
for key, value := range header {
req.Header.Set(key, value)
}
if body != "" && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
response, err := c.http.Do(req)
if err != nil {
// gRPC-style sentinel so the host-side transient-error handling keeps working.
return nil, fmt.Errorf("etcd connection error: %w", err)
}
return response, nil
}
// doAt performs a v2 API request against a specific endpoint.
func (c *authenticatedClient) doAt(ctx context.Context, method, endpoint, path, body string) ([]byte, *http.Response, error) {
response, err := c.requestAt(ctx, method, endpoint, path, body, nil)
if err != nil {
return nil, nil, 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
- Inspect the wrapped %w cause to distinguish refused vs timeout vs TLS failure.
- Verify the endpoint is reachable: curl http://host:2379/version (or https as configured).
- Confirm the client port 2379 (not the peer port 2380) and correct scheme http vs https.
- Retry with backoff for transient network conditions; the prefix supports the library's transient-error classification.
Example fix
// before endpoint = "https://127.0.0.1:2379" // server has no TLS // after endpoint = "http://127.0.0.1:2379"
Defensive patterns
Strategy: retry
Validate before calling
conn, err := net.DialTimeout("tcp", host, 2*time.Second)
if err != nil { return fmt.Errorf("etcd endpoint %s unreachable: %v", host, err) }
conn.Close() Try / catch
_, err := client.request(ctx, "GET", "/keys/foo")
if err != nil && strings.HasPrefix(err.Error(), "etcd connection error:") {
return retryWithBackoff(ctx, 5, func() error { _, err := client.request(ctx, "GET", "/keys/foo"); return err })
} Prevention
- Pre-flight TCP check of host:2379
- Match scheme (http/https) to server TLS config
- Rely on the sentinel prefix for transient-error classification instead of string-matching the cause
When it happens
Trigger: Any v2 API call (via request or doAt) where c.http.Do fails: endpoint unreachable, port closed, TLS mismatch, network partition, or context deadline exceeded.
Common situations: etcd not running on the configured host:port, wrong port (2379 client vs 2380 peer), firewall blocking, TLS config pointing at a plaintext endpoint or vice versa, container networking issues.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
- Not connected
- ETCD_HISTORY_TIMEOUT
- ZooKeeper connection timed out before a session was establis
- Connection timed out
- connect to ZooKeeper: %w
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/a34291d10c8a8aa5.
Report an issue: GitHub.