t8y2/dbx · error
lease, ttl, and preserveLease cannot be specified together
Error message
lease, ttl, and preserveLease cannot be specified together
What it means
Input-validation error thrown by the put operation at kv.go:250. The etcd lease model for a put accepts exactly one lease source: an existing lease ID (lease), a fresh lease granted from a TTL (ttl), or preserving the key's current lease (preserveLease). Supplying more than one of these together is ambiguous, so the driver rejects the call before touching etcd.
Solutions
- Remove all but one of lease, ttl, preserveLease from the put params — keep exactly one lease strategy (or none for a permanent key).
- If you want the key to keep its existing lease, pass preserveLease:true and drop lease/ttl.
- If you need a fresh expiring key, pass ttl only and omit lease/preserveLease.
- If attaching to a pre-granted lease, pass lease (the lease ID number) only.
Example fix
// before
put(session, key, value, { ttl: 60, preserveLease: true })
// after
put(session, key, value, { preserveLease: true }) Defensive patterns
Strategy: validation
Validate before calling
function validatePutLeaseOptions(opts) {
const n = [opts.lease != null, opts.ttl != null, opts.preserveLease === true].filter(Boolean).length;
if (n > 1) throw new Error('specify at most one of lease, ttl, preserveLease');
} Type guard
function hasConflictingLeaseOptions(p) {
return (p.lease != null && p.ttl != null) || (p.preserveLease === true && (p.lease != null || p.ttl != null));
} Try / catch
try { return put(session, key, value, opts); }
catch (e) {
if (String(e).includes('cannot be specified together')) {
const { lease, ttl, ...rest } = opts;
return put(session, key, value, rest); // drop conflicting lease opts
}
throw e;
} Prevention
- Centralize put option building so lease strategy is chosen in one place.
- Never merge default lease options with user-supplied ones blindly.
- Pick one idiom per call site: ttl for expiring keys, preserveLease for updates, lease for pre-granted IDs.
- Add a unit test asserting conflicting combos are rejected.
When it happens
Trigger: Any put call whose params include both lease and ttl; or preserveLease=true together with either lease or ttl.
Common situations: Template/config merging that accumulates lease options from multiple layers (default ttl plus explicit lease); migrating code from ttl-based puts to preserveLease and leaving the old parameter in place; copy-pasted option objects.
Related errors
- ETCD_INVALID_%s
- ETCD_NEWKEY_REQUIRED
- id must be a positive integer or 0
- ttl must be a positive integer
- agentSessionId is required
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/55e3ac1b8c4c060b.
Report an issue: GitHub.
Appendix: source
Thrown at agents/drivers/etcd-go/kv.go:250
return nil, err
}
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")
leaseValue := longOrNull(params, "lease")
ttlValue := longOrNull(params, "ttl")
preserveLease := boolOrDefault(params, "preserveLease", false)
hasLease := leaseValue != nil
hasTtl := ttlValue != nil
if (hasLease && hasTtl) || (preserveLease && (hasLease || hasTtl)) {
return nil, errors.New("lease, ttl, and preserveLease cannot be specified together")
}
ctx, cancel := s.beginOperation()
defer s.endOperation(cancel)
if preserveLease {
revision, err := putPreservingLease(client, ctx, key, value)
if err != nil {
return nil, err
}
return map[string]any{"revision": longString(revision)}, nil
}
var leaseID clientv3.LeaseID
var grantedLeaseID clientv3.LeaseID
if hasTtl {
if *ttlValue <= 0 {
return nil, errors.New("ttl must be a positive integer")
}View on GitHub (pinned to c0390bff16)