{"record":{"id":"c77928672a50b33b","repo":"t8y2/dbx","slug":"ttl-must-be-a-positive-integer","errorCode":null,"errorMessage":"ttl must be a positive integer","messagePattern":"ttl must be a positive integer","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agents/drivers/etcd-go/kv.go","lineNumber":267,"sourceCode":"\tif (hasLease && hasTtl) || (preserveLease && (hasLease || hasTtl)) {\n\t\treturn nil, errors.New(\"lease, ttl, and preserveLease cannot be specified together\")\n\t}\n\n\tctx, cancel := s.beginOperation()\n\tdefer s.endOperation(cancel)\n\tif preserveLease {\n\t\trevision, err := putPreservingLease(client, ctx, key, value)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn map[string]any{\"revision\": longString(revision)}, nil\n\t}\n\n\tvar leaseID clientv3.LeaseID\n\tvar grantedLeaseID clientv3.LeaseID\n\tif hasTtl {\n\t\tif *ttlValue <= 0 {\n\t\t\treturn nil, errors.New(\"ttl must be a positive integer\")\n\t\t}\n\t\tgrant, err := client.Lease.Grant(ctx, *ttlValue)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgrantedLeaseID = grant.ID\n\t\tleaseID = grant.ID\n\t} else if hasLease {\n\t\tleaseID = clientv3.LeaseID(*leaseValue)\n\t}\n\n\trevision, err := func() (int64, error) {\n\t\tif expectedModRevision != nil || expectedCreateRevision != nil {\n\t\t\tvar comparisons []clientv3.Cmp\n\t\t\tif expectedModRevision != nil {\n\t\t\t\tcomparisons = append(comparisons, clientv3.Compare(clientv3.ModRevision(key), \"=\", *expectedModRevision))\n\t\t\t}\n\t\t\tif expectedCreateRevision != nil {","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/etcd-go/kv.go#L249-L285","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure ttl is a positive integer (seconds, minimum 1) before calling put.","Treat ttl <= 0 as 'no lease': omit the parameter entirely instead of sending 0.","Clamp or guard in caller code: ttl > 0 ? { ttl } : {}.","Fix the upstream calculation (e.g. Math.max(1, expirySeconds - now)) if TTL is derived."],"exampleFix":"// before\nput(session, key, value, { ttl: ttlFromConfig }) // ttlFromConfig = 0\n// after\nconst opts = ttlFromConfig > 0 ? { ttl: ttlFromConfig } : {};\nput(session, key, value, opts)","handlingStrategy":"validation","validationCode":"if (opts.ttl != null && (!Number.isInteger(opts.ttl) || opts.ttl <= 0)) {\n  throw new Error('ttl must be a positive integer (seconds)');\n}","typeGuard":"function isValidTtl(ttl) { return ttl != null && Number.isInteger(ttl) && ttl > 0; }","tryCatchPattern":"try { return put(session, key, value, opts); }\ncatch (e) {\n  if (String(e).includes('ttl must be a positive integer'))\n    return put(session, key, value, { ...opts, ttl: Math.max(1, opts.ttl) });\n  throw e;\n}","preventionTips":["Treat ttl <= 0 as 'no lease' and omit the field, don't send 0.","Type-check ttl at config-load time (positive integer).","Guard derived TTLs: Math.max(1, expirySeconds - nowSeconds).","Document that ttl is in whole seconds, minimum 1."],"tags":["etcd","lease","ttl","validation"],"backgroundTag":"invalid-ttl-value","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}