t8y2/dbx · error

ttl must be a positive integer

Error message

ttl must be a positive integer

What it means

Validation error thrown by put at kv.go:267 when the ttl parameter is provided but is zero or negative. etcd's Lease.Grant requires a TTL of at least 1 second; the driver checks this before calling Grant so an obviously invalid ttl fails fast instead of producing a raw etcd gRPC error.

Source

Thrown at agents/drivers/etcd-go/kv.go:267

	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")
		}
		grant, err := client.Lease.Grant(ctx, *ttlValue)
		if err != nil {
			return nil, err
		}
		grantedLeaseID = grant.ID
		leaseID = grant.ID
	} else if hasLease {
		leaseID = clientv3.LeaseID(*leaseValue)
	}

	revision, err := func() (int64, error) {
		if expectedModRevision != nil || expectedCreateRevision != nil {
			var comparisons []clientv3.Cmp
			if expectedModRevision != nil {
				comparisons = append(comparisons, clientv3.Compare(clientv3.ModRevision(key), "=", *expectedModRevision))
			}
			if expectedCreateRevision != nil {

View on GitHub (pinned to c0390bff16)

Solutions

  1. Ensure ttl is a positive integer (seconds, minimum 1) before calling put.
  2. Treat ttl <= 0 as 'no lease': omit the parameter entirely instead of sending 0.
  3. Clamp or guard in caller code: ttl > 0 ? { ttl } : {}.
  4. Fix the upstream calculation (e.g. Math.max(1, expirySeconds - now)) if TTL is derived.

Example fix

// before
put(session, key, value, { ttl: ttlFromConfig }) // ttlFromConfig = 0
// after
const opts = ttlFromConfig > 0 ? { ttl: ttlFromConfig } : {};
put(session, key, value, opts)
Defensive patterns

Strategy: validation

Validate before calling

if (opts.ttl != null && (!Number.isInteger(opts.ttl) || opts.ttl <= 0)) {
  throw new Error('ttl must be a positive integer (seconds)');
}

Type guard

function isValidTtl(ttl) { return ttl != null && Number.isInteger(ttl) && ttl > 0; }

Try / catch

try { return put(session, key, value, opts); }
catch (e) {
  if (String(e).includes('ttl must be a positive integer'))
    return put(session, key, value, { ...opts, ttl: Math.max(1, opts.ttl) });
  throw e;
}

Prevention

When it happens

Trigger: Calling put with ttl: 0, ttl: -1, or any ttl <= 0 — commonly the result of an unset/zero-valued numeric variable being passed through as the ttl parameter.

Common situations: Defaults where ttl defaults to 0 meaning 'no ttl' but is passed unconditionally; computing TTL from a difference (expiry - now) that lands at 0 or negative; deserializing configs where ttl is '0'/'-1' as a sentinel for disabled.

Related errors


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