t8y2/dbx · error

lease, ttl, and preserveLease cannot be specified together

Error message

lease, ttl, and preserveLease cannot be specified together

What it means

The etcd v2 put handler accepts three mutually exclusive lease-related options: an explicit lease, a TTL, and preserveLease. Because the v2 API has no lease objects, at most one option may be given; specifying any two together is rejected with this error before any HTTP request is made.

Source

Thrown at agents/drivers/etcd2-go/kv.go:200

	}
	key, err := keyBytesParam(params)
	if err != nil {
		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))

View on GitHub (pinned to c0390bff16)

Solutions

  1. Send only one of lease, ttl, or preserveLease per put call
  2. Drop lease fields entirely for v2 (see ETCD_V2_LEASE_UNSUPPORTED) and use ttl
  3. Strip nil/default-valued lease options before dispatching so merged configs do not collide

Example fix

// before
{ "key":"k", "value":"v", "lease": 123, "ttl": 60 }
// after
{ "key":"k", "value":"v", "ttl": 60 }
Defensive patterns

Strategy: validation

Validate before calling

opts := 0
if hasLease { opts++ }
if hasTtl { opts++ }
if preserveLease { opts++ }
if opts > 1 { return errors.New("send at most one of lease, ttl, preserveLease") }

Try / catch

_, err := session.handle(ctx, "put", params)
if err != nil && strings.Contains(err.Error(), "cannot be specified together") {
	return fmt.Errorf("trim put options to a single lease mechanism; for v2 use ttl")
}

Prevention

When it happens

Trigger: Calling put with lease AND ttl, preserveLease AND lease, or preserveLease AND ttl in the same request.

Common situations: Config layers merging defaults (a default ttl) with per-request lease options; migrating v3 code that passes lease IDs; a generic client that always forwards all optional fields.

Related errors


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