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

  1. Validate the ID is a canonical 36-char dashed UUID before calling IDToIndex
  2. Use the ID exactly as returned by the API (Event.ID from Event.List) rather than reconstructing it
  3. If IDs may be non-standard, wrap the call in a recovering helper that returns an error instead of crashing
  4. 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

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


AI-assisted analysis of hashicorp/consul@2397ff0d76 (2026-08-15). Data as JSON: /api/errors/4e33e67b53c6c45b. Report an issue: GitHub.