{"record":{"id":"3865731ba4edb206","repo":"etcd-io/etcd","slug":"bad-value-v-of-type-t","errorCode":null,"errorMessage":"bad value %v of type %T","messagePattern":"bad value (.+?) of type %T","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/v3/compare.go","lineNumber":177,"sourceCode":"}\n\n// WithPrefix sets the comparison to scan all keys prefixed by the key.\nfunc (cmp Cmp) WithPrefix() Cmp {\n\tcmp = cmp.Clone()\n\tcmp.ensureCompare()\n\tcmp.c.RangeEnd = getPrefix(cmp.c.GetKey())\n\treturn cmp\n}\n\n// mustInt64 panics if val isn't an int or int64. It returns an int64 otherwise.\nfunc mustInt64(val any) int64 {\n\tif v, ok := val.(int64); ok {\n\t\treturn v\n\t}\n\tif v, ok := val.(int); ok {\n\t\treturn int64(v)\n\t}\n\tpanic(fmt.Sprintf(\"bad value %v of type %T\", val, val))\n}\n\n// mustInt64orLeaseID panics if val isn't a LeaseID, int or int64. It returns an\n// int64 otherwise.\nfunc mustInt64orLeaseID(val any) int64 {\n\tif v, ok := val.(LeaseID); ok {\n\t\treturn int64(v)\n\t}\n\treturn mustInt64(val)\n}\n\nfunc cloneCompare(c *pb.Compare) *pb.Compare {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn proto.Clone(c).(*pb.Compare)\n}\n","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/etcd-io/etcd/blob/f744d457f484e9f748a0700b48ef96dcf792df33/client/v3/compare.go#L159-L195","documentation":"mustInt64 is the coercion helper behind CompareVersion, CompareCreated, and CompareModified comparisons. It accepts only the exact dynamic types int64, int (and LeaseID via mustInt64orLeaseID for lease targets); every other type panics with \"bad value %v of type %T\". The panic message deliberately includes the offending value and its Go type so the bad call site is obvious.","triggerScenarios":"clientv3.Compare(clientv3.CompareVersion(k), \"=\", int32(3)), uint64(3), \"3\", or a JSON-decoded float64. Also passing a typed alias like type MyInt uint16. Note int on 64-bit platforms works, but narrower/wider widths (int8..int32, uint*, float*) do not.","commonSituations":"Decoding JSON into map[string]interface{} where numbers become float64; using uint64 because etcd revisions/leases are documented as uint64 on the wire; protobuf-generated fields of type int32; version counters stored as custom integer types.","solutions":["Cast to int64 at the call site: clientv3.Compare(clientv3.CompareVersion(k), \"=\", int64(myUint64)).","For JSON-derived numbers, convert float64 via int64(f) after range checking.","For lease comparisons, pass a clientv3.LeaseID directly (mustInt64orLeaseID accepts it) rather than a raw integer type."],"exampleFix":"// before\nrev := resp.Header.Revision // int64 is fine, but:\ncmp := clientv3.Compare(clientv3.CompareVersion(k), \"=\", uint64(resp.Count)) // panics: bad value N of type uint64\n\n// after\ncmp := clientv3.Compare(clientv3.CompareVersion(k), \"=\", int64(resp.Count))","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func toInt64(v any) (int64, error) {\n\tswitch n := v.(type) {\n\tcase int64:\n\t\treturn n, nil\n\tcase int:\n\t\treturn int64(n), nil\n\tcase uint64:\n\t\treturn int64(n), nil // explicit widening decision at your boundary\n\tdefault:\n\t\treturn 0, fmt.Errorf(\"unsupported numeric type %T\", v)\n\t}\n}","tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        return fmt.Errorf(\"Compare panicked (bad numeric value): %v\", r)\n    }\n}()","preventionTips":["Always call clientv3 numeric compares with an explicit int64(...) cast at the call site.","uint64 revisions/leases from wire types must be converted; they are never accepted directly (LeaseID excepted).","Convert JSON float64 numbers via int64(f) before using them in compares."],"tags":["go","etcd","clientv3","compare","panic","type-mismatch","coercion"],"backgroundTag":null,"analyzedSha":"f744d457f484e9f748a0700b48ef96dcf792df33","analyzedAt":"2026-08-15T09:39:50.079Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}