t8y2/dbx · error

ttl must be a positive integer

Error message

ttl must be a positive integer

What it means

put validates that a supplied TTL is a positive integer. Since etcd v2 TTLs are seconds sent in the form payload, zero or negative values are meaningless and rejected before the request is built. This check runs only when ttl was actually provided.

Source

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

	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))
	}
	if expectedCreateRevision != nil && *expectedCreateRevision == 0 {
		form.Set("prevExist", "false")
	}

	ctx, cancel := s.beginOperation()
	defer s.endOperation(cancel)
	body, _, err := client.do(ctx, http.MethodPut, v2KeyPath(key), form.Encode(), nil)
	if err != nil {

View on GitHub (pinned to c0390bff16)

Solutions

  1. Omit the ttl parameter entirely for keys that should not expire
  2. Send ttl >= 1 (seconds)
  3. Fix defaults so 'unset' is represented by a nil/absent ttl, not 0 or -1

Example fix

// before
{ "key":"k", "value":"v", "ttl": 0 }
// after
{ "key":"k", "value":"v" } // omit ttl for no expiry
Defensive patterns

Strategy: validation

Validate before calling

if ttl != nil && *ttl <= 0 {
	return errors.New("ttl must be >= 1; omit it for no expiry")
}

Try / catch

_, err := session.handle(ctx, "put", params)
if err != nil && strings.Contains(err.Error(), "ttl must be a positive integer") {
	return fmt.Errorf("fix ttl (got %v): use >=1 or omit the field", params["ttl"])
}

Prevention

When it happens

Trigger: Calling put with ttl = 0 or ttl < 0 (ttl absent skips the check entirely).

Common situations: Using ttl:0 to mean 'no expiry' (the correct way is to omit the field); integer division or defaulting logic producing 0; config defaults of -1 intended as 'unset'.

Understand the failure class

Background: "must be positive", "Invalid value": how libraries reject invalid parameter values (ValueError, ArgumentError, INVALID_PARAMETER_VALUE) — this error's family across 28 libraries.

Related errors


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