hashicorp/consul · error
Failed to convert
Error message
Failed to convert
What it means
Event.IDToIndex converts a Consul event ID (a UUID) into a synthetic index so clients can blocking-query over the event list (api/watch uses it at funcs.go:259). It slices the canonical 36-character dashed UUID into two 16-hex-digit halves and XORs the parsed values. If any segment is not valid hexadecimal the ParseUint fails and the function panics; strings shorter than 36 chars panic even earlier with a slice-bounds error.
Source
Thrown at api/event.go:111
var entries []*UserEvent
if err := decodeBody(resp, &entries); err != nil {
return nil, nil, err
}
return entries, qm, nil
}
// IDToIndex is a bit of a hack. This simulates the index generation to
// convert an event ID into a WaitIndex.
func (e *Event) IDToIndex(uuid string) uint64 {
lower := uuid[0:8] + uuid[9:13] + uuid[14:18]
upper := uuid[19:23] + uuid[24:36]
lowVal, err := strconv.ParseUint(lower, 16, 64)
if err != nil {
panic("Failed to convert " + lower)
}
highVal, err := strconv.ParseUint(upper, 16, 64)
if err != nil {
panic("Failed to convert " + upper)
}
return lowVal ^ highVal
}
View on GitHub (pinned to 2397ff0d76)
Solutions
- Validate the ID is a canonical 36-char dashed UUID before calling IDToIndex
- Use the ID exactly as returned by the API (Event.ID from Event.List) rather than reconstructing it
- If IDs may be non-standard, wrap the call in a recovering helper that returns an error instead of crashing
- Fix the upstream producer generating malformed IDs
Example fix
// before
idx := events.IDToIndex(rawID) // panics on malformed ID
// after
if !isValidUUID(rawID) {
return fmt.Errorf("event id %q is not a canonical UUID", rawID)
}
idx := events.IDToIndex(rawID) Defensive patterns
Strategy: validation
Validate before calling
// guard before calling IDToIndex
var canonicalUUID = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
func safeIDToIndex(e *api.Event, id string) (uint64, error) {
if !canonicalUUID.MatchString(id) {
return 0, fmt.Errorf("event id %q is not a canonical UUID", id)
}
return e.IDToIndex(id), nil
} Type guard
func isCanonicalUUID(s string) bool {
if len(s) != 36 || s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return false
}
for _, r := range s {
if r == '-' {
continue
}
if !strings.ContainsRune("0123456789abcdefABCDEF", r) {
return false
}
}
return true
} Try / catch
// Go has no try/catch; wrap the call with a recovering helper in
code that ingests external IDs
func recoveringIDToIndex(e *api.Event, id string) (idx uint64, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("IDToIndex panicked for id %q: %v", id, r)
}
}()
return e.IDToIndex(id), nil
} Prevention
- Only feed IDs obtained from Event.List/Event.Fire output into IDToIndex
- Validate external/config-supplied event IDs against a UUID pattern before use
- In watch code (api/watch), keep goroutines resilient: a malformed ID crashes the watch handler otherwise
When it happens
Trigger: Calling IDToIndex with a non-canonical ID: empty string, non-hex characters, a truncated ID, or an ID produced by a non-Consul producer. The events API returns canonical UUIDs, so this fires on malformed or hand-constructed input passed by caller code.
Common situations: Passing user-supplied or config-file event IDs straight into IDToIndex; blocking-query watch code receiving unexpected event payloads; test fixtures using fake IDs like 'test-event'.
Related errors
- ClusterSize not set
- failed to decode request: %v
- failed to decode batch updates: %v
- all indexers must have a non-empty name
- no indexer was supplied when creating a new cache Index
AI-assisted analysis of hashicorp/consul@2397ff0d76 (2026-08-15).
Data as JSON: /api/errors/4e33e67b53c6c45b.
Report an issue: GitHub.