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

  1. Remove the lease parameter and use ttl for expiry behavior
  2. Migrate to the etcd v3 driver if lease semantics are required
  3. 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

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


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