{"record":{"id":"185e8e01abee725e","repo":"t8y2/dbx","slug":"ttl-must-be-a-positive-integer-185e8e","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/etcd2-go/kv.go","lineNumber":212,"sourceCode":"\tpreserveLease := boolOrDefault(params, \"preserveLease\", false)\n\n\tleaseValue := longOrNull(params, \"lease\")\n\thasLease := leaseValue != nil\n\thasTtl := ttlValue != nil\n\tif (hasLease && hasTtl) || (preserveLease && (hasLease || hasTtl)) {\n\t\treturn nil, errors.New(\"lease, ttl, and preserveLease cannot be specified together\")\n\t}\n\tif hasLease {\n\t\treturn nil, errors.New(\"ETCD_V2_LEASE_UNSUPPORTED: the etcd v2 API has no lease objects; use ttl instead\")\n\t}\n\tif preserveLease {\n\t\treturn nil, errors.New(\"ETCD_V2_LEASE_UNSUPPORTED: the etcd v2 API has no lease objects; use ttl instead\")\n\t}\n\tif expectedCreateRevision != nil && *expectedCreateRevision != 0 {\n\t\treturn nil, errors.New(\"ETCD_V2_CAS_UNSUPPORTED: the etcd v2 API cannot compare createdIndex; use expectedModRevision\")\n\t}\n\tif hasTtl && *ttlValue <= 0 {\n\t\treturn nil, errors.New(\"ttl must be a positive integer\")\n\t}\n\n\tform := url.Values{}\n\tform.Set(\"value\", value)\n\tif hasTtl {\n\t\tform.Set(\"ttl\", strconv.FormatInt(*ttlValue, 10))\n\t}\n\tif expectedModRevision != nil {\n\t\tform.Set(\"prevIndex\", strconv.FormatInt(*expectedModRevision, 10))\n\t}\n\tif expectedCreateRevision != nil && *expectedCreateRevision == 0 {\n\t\tform.Set(\"prevExist\", \"false\")\n\t}\n\n\tctx, cancel := s.beginOperation()\n\tdefer s.endOperation(cancel)\n\tbody, _, err := client.do(ctx, http.MethodPut, v2KeyPath(key), form.Encode(), nil)\n\tif err != nil {","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/etcd2-go/kv.go#L194-L230","documentation":"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.","triggerScenarios":"Calling put with ttl = 0 or ttl < 0 (ttl absent skips the check entirely).","commonSituations":"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'.","solutions":["Omit the ttl parameter entirely for keys that should not expire","Send ttl >= 1 (seconds)","Fix defaults so 'unset' is represented by a nil/absent ttl, not 0 or -1"],"exampleFix":"// before\n{ \"key\":\"k\", \"value\":\"v\", \"ttl\": 0 }\n// after\n{ \"key\":\"k\", \"value\":\"v\" } // omit ttl for no expiry","handlingStrategy":"validation","validationCode":"if ttl != nil && *ttl <= 0 {\n\treturn errors.New(\"ttl must be >= 1; omit it for no expiry\")\n}","typeGuard":null,"tryCatchPattern":"_, err := session.handle(ctx, \"put\", params)\nif err != nil && strings.Contains(err.Error(), \"ttl must be a positive integer\") {\n\treturn fmt.Errorf(\"fix ttl (got %v): use >=1 or omit the field\", params[\"ttl\"])\n}","preventionTips":["Use typed ttl params so 0/negatives are caught at compile time where possible","Represent 'no expiry' by omitting ttl, never by ttl:0","Clamp or reject non-positive ttl in your request builder","Add table tests for ttl edge values 0, -1, 1"],"tags":["etcd","validation","parameters","ttl"],"backgroundTag":"invalid-parameter-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"}