etcd-io/etcd · error
bad value %v of type %T
Error message
bad value %v of type %T
What it means
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.
Source
Thrown at client/v3/compare.go:177
}
// WithPrefix sets the comparison to scan all keys prefixed by the key.
func (cmp Cmp) WithPrefix() Cmp {
cmp = cmp.Clone()
cmp.ensureCompare()
cmp.c.RangeEnd = getPrefix(cmp.c.GetKey())
return cmp
}
// mustInt64 panics if val isn't an int or int64. It returns an int64 otherwise.
func mustInt64(val any) int64 {
if v, ok := val.(int64); ok {
return v
}
if v, ok := val.(int); ok {
return int64(v)
}
panic(fmt.Sprintf("bad value %v of type %T", val, val))
}
// mustInt64orLeaseID panics if val isn't a LeaseID, int or int64. It returns an
// int64 otherwise.
func mustInt64orLeaseID(val any) int64 {
if v, ok := val.(LeaseID); ok {
return int64(v)
}
return mustInt64(val)
}
func cloneCompare(c *pb.Compare) *pb.Compare {
if c == nil {
return nil
}
return proto.Clone(c).(*pb.Compare)
}
View on GitHub (pinned to f744d457f4)
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.
Example fix
// before rev := resp.Header.Revision // int64 is fine, but: cmp := clientv3.Compare(clientv3.CompareVersion(k), "=", uint64(resp.Count)) // panics: bad value N of type uint64 // after cmp := clientv3.Compare(clientv3.CompareVersion(k), "=", int64(resp.Count))
Defensive patterns
Strategy: type-guard
Type guard
func toInt64(v any) (int64, error) {
switch n := v.(type) {
case int64:
return n, nil
case int:
return int64(n), nil
case uint64:
return int64(n), nil // explicit widening decision at your boundary
default:
return 0, fmt.Errorf("unsupported numeric type %T", v)
}
} Try / catch
defer func() {
if r := recover(); r != nil {
return fmt.Errorf("Compare panicked (bad numeric value): %v", r)
}
}() Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- bad compare value
- Unknown result op
- unsupported stm
- unexpected revision = 0. Calling SyncUpdates before SyncBase
- `WithPrefix` and `WithFromKey` cannot be set at the same tim
AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15).
Data as JSON: /api/errors/3865731ba4edb206.
Report an issue: GitHub.