{"record":{"id":"c281383d9436c40b","repo":"t8y2/dbx","slug":"etcd-v2-api-disabled","errorCode":"ETCD_V2_API_DISABLED","errorMessage":"ETCD_V2_API_DISABLED: %s does not expose the etcd v2 API (removed in etcd 3.6+)","messagePattern":"ETCD_V2_API_DISABLED: (.+?) does not expose the etcd v2 API \\(removed in etcd 3\\.6\\+\\)","errorType":"error_code","errorClass":null,"httpStatus":404,"severity":"error","filePath":"agents/drivers/etcd2-go/client.go","lineNumber":267,"sourceCode":"\treturn etcdVersion{etcdserver: parsed.Etcdserver, etcdcluster: parsed.Etcdcluster}, nil\n}\n\n// probeV2 checks that the v2 keys API is actually served. A 403 proves the\n// channel and credentials reached etcd, mirroring the v3 agent's\n// PERMISSION_DENIED handling for restricted users.\nfunc (c *authenticatedClient) probeV2(ctx context.Context) (map[string]any, error) {\n\tresponse, err := c.request(ctx, http.MethodGet, \"/v2/members\", \"\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer drainClose(response.Body)\n\tswitch response.StatusCode {\n\tcase http.StatusOK:\n\t\treturn map[string]any{\"ok\": true, \"endpoint\": c.endpoint}, nil\n\tcase http.StatusForbidden:\n\t\treturn map[string]any{\"ok\": true, \"endpoint\": c.endpoint, \"limited\": true}, nil\n\tcase http.StatusNotFound:\n\t\treturn nil, fmt.Errorf(\"ETCD_V2_API_DISABLED: %s does not expose the etcd v2 API (removed in etcd 3.6+)\", c.endpoint)\n\tcase http.StatusUnauthorized:\n\t\treturn nil, fmt.Errorf(\"ETCD_UNAUTHENTICATED: authentication failed against %s\", c.endpoint)\n\tdefault:\n\t\tbody, _ := io.ReadAll(io.LimitReader(response.Body, 4096))\n\t\treturn nil, fmt.Errorf(\"etcd v2 probe against %s failed: HTTP %d %s\", c.endpoint, response.StatusCode, strings.TrimSpace(string(body)))\n\t}\n}\n\n// do performs a v2 API request and returns the body. Non-2xx responses are\n// converted into etcdError values carrying the server's errorCode/message.\nfunc (c *authenticatedClient) do(ctx context.Context, method, path, body string, header map[string]string) ([]byte, *http.Response, error) {\n\tresponse, err := c.request(ctx, method, path, body, header)\n\tif err != nil {\n\t\treturn nil, response, err\n\t}\n\tpayload, readErr := io.ReadAll(response.Body)\n\t_ = response.Body.Close()\n\tif response.StatusCode < 200 || response.StatusCode >= 300 {","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/etcd2-go/client.go#L249-L285","documentation":"The etcd2 agent probes the v2 keys API (/v2/keys) to confirm it is served; etcd 3.6+ removed the v2 API, and such servers respond with HTTP 404. The agent converts that into the coded error ETCD_V2_API_DISABLED identifying the endpoint, so users get a clear diagnosis instead of confusing downstream failures.","triggerScenarios":"Connecting (probeClient -> probeV2) to an etcd server that returns HTTP 404 for /v2/keys — i.e. etcd 3.6 or newer with --enable-v2 not enabled/removed.","commonSituations":"Upgrading an etcd cluster from 3.5 to 3.6+ where the v2 API was removed; using the etcd2 agent driver against a modern etcd cluster by mistake; a reverse proxy returning 404 for /v2/keys because the route does not exist.","solutions":["Use the v3 agent driver (agents/drivers/etcd-go) instead of the etcd2 driver against etcd 3.6+","Downgrade/keep an etcd 3.5.x server if the v2 API is genuinely required","On servers that still support the flag, start etcd with --enable-v2=true","Check for a proxy stripping /v2/ paths and route v2 requests directly to etcd"],"exampleFix":"// before\nagent := etcd2agent.New(\"https://etcd:2379\") // etcd 3.6: no v2 API\n\n// after\nagent := etcdagent.New(\"https://etcd:2379\") // v3 driver for etcd >= 3.6","handlingStrategy":"fallback","validationCode":"// detect v2 API availability before connecting with the etcd2 driver\nresp, err := http.Get(endpoint + \"/v2/keys\")\nif err == nil && resp.StatusCode == http.StatusNotFound {\n    return errors.New(\"v2 API unavailable: use the v3 agent driver\")\n}","typeGuard":"func isV2Disabled(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"ETCD_V2_API_DISABLED\")\n}","tryCatchPattern":"conn, err := etcd2agent.Connect(endpoint)\nif isV2Disabled(err) {\n    conn, err = etcdagent.Connect(endpoint) // fall back to v3 driver\n}","preventionTips":["Match the driver to the server version (etcd2 driver only for servers exposing /v2)","Pin etcd 3.5.x if the v2 API is required, or enable --enable-v2 where supported","Run a connectivity preflight that checks /v2/keys reachability before deploying the etcd2 driver"],"tags":["go","etcd","api-version","compatibility","upgrade"],"backgroundTag":"api-version-unsupported","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}