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
- Send only one of lease, ttl, or preserveLease per put call
- Drop lease fields entirely for v2 (see ETCD_V2_LEASE_UNSUPPORTED) and use ttl
- 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
- Use one struct with mutually exclusive fields; validate before send
- Strip zero-value/default lease options before building the request
- Keep a v2 adapter that drops lease fields and maps them to ttl
- Test merged-config output to catch accidental option collisions
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
- ETCD_WATCH_SCOPE_INVALID
- ttl must be a positive integer
- ETCD_WATCH_SCOPE_INVALID
- ETCD_INVALID_%s
- ETCD_%s_REQUIRED
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/33c1c10b88258ca7.
Report an issue: GitHub.