t8y2/dbx · error
ETCD_V2_LEASE_UNSUPPORTED
ETCD_V2_LEASE_UNSUPPORTED
Error message
ETCD_V2_LEASE_UNSUPPORTED: the etcd v2 API has no lease objects; use ttl instead
What it means
The etcd v2 API has no lease objects (those exist only in v3), so a put that specifies a lease can never be honored. After the mutual-exclusion check passes, a present lease value is rejected with this code. The message directs callers to use ttl instead, which v2 supports natively.
Source
Thrown at agents/drivers/etcd2-go/kv.go:203
return nil, err
}
value, err := parseValueObject(rawObject(params, "value"))
if err != nil {
return nil, err
}
expectedModRevision := longOrNull(params, "expectedModRevision")
expectedCreateRevision := longOrNull(params, "expectedCreateRevision")
ttlValue := longOrNull(params, "ttl")
preserveLease := boolOrDefault(params, "preserveLease", false)
leaseValue := longOrNull(params, "lease")
hasLease := leaseValue != nil
hasTtl := ttlValue != nil
if (hasLease && hasTtl) || (preserveLease && (hasLease || hasTtl)) {
return nil, errors.New("lease, ttl, and preserveLease cannot be specified together")
}
if hasLease {
return nil, errors.New("ETCD_V2_LEASE_UNSUPPORTED: the etcd v2 API has no lease objects; use ttl instead")
}
if preserveLease {
return nil, errors.New("ETCD_V2_LEASE_UNSUPPORTED: the etcd v2 API has no lease objects; use ttl instead")
}
if expectedCreateRevision != nil && *expectedCreateRevision != 0 {
return nil, errors.New("ETCD_V2_CAS_UNSUPPORTED: the etcd v2 API cannot compare createdIndex; use expectedModRevision")
}
if hasTtl && *ttlValue <= 0 {
return nil, errors.New("ttl must be a positive integer")
}
form := url.Values{}
form.Set("value", value)
if hasTtl {
form.Set("ttl", strconv.FormatInt(*ttlValue, 10))
}
if expectedModRevision != nil {
form.Set("prevIndex", strconv.FormatInt(*expectedModRevision, 10))View on GitHub (pinned to c0390bff16)
Solutions
- Remove the lease parameter and use ttl for expiry behavior
- Migrate to the etcd v3 driver if lease semantics are required
- Strip lease fields in a wrapper layer before calling v2 put
Example fix
// before
{ "key":"k", "value":"v", "lease": 758784123 }
// after
{ "key":"k", "value":"v", "ttl": 60 } Defensive patterns
Strategy: validation
Validate before calling
if lease != nil {
return errors.New("etcd v2 does not support lease; use ttl")
} Try / catch
_, err := session.handle(ctx, "put", params)
if err != nil && strings.Contains(err.Error(), "ETCD_V2_LEASE_UNSUPPORTED") {
params = dropLeaseAddTTL(params)
_, err = session.handle(ctx, "put", params)
} Prevention
- Never send lease fields to v2 sessions; centralize request building
- Convert desired expiry to ttl seconds at the call site
- If leases are required, target the v3 driver
- Add a lint/test that v2 request payloads contain no 'lease' key
When it happens
Trigger: Calling put with a non-nil 'lease' parameter (and not conflicting with ttl/preserveLease, which would fail earlier with the mutual-exclusion error).
Common situations: Reusing v3 client code that attaches lease IDs; a framework that always includes a lease field copied from cluster defaults; following v3 documentation while targeting a v2 endpoint.
Related errors
- ETCD_V2_CAS_UNSUPPORTED
- lease, ttl, and preserveLease cannot be specified together
- ttl must be a positive integer
- Cannot preserve lease: key does not exist or has no lease
- Cannot preserve lease: key changed concurrently; retry the s
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/78b872cee8431b5c.
Report an issue: GitHub.