cilium/cilium · error

recursion on type %s

Error message

recursion on type %s

What it means

ebpfFields tracks visited reflect.Types to prevent infinite recursion when a struct type contains itself (directly or through a cycle of 'ebpf'-tagged pointer fields). Encountering an already-visited type aborts with this recursion error instead of looping forever.

Source

Thrown at pkg/bpf/analyze/fields.go:47

// structField represents a struct field containing the ebpf struct tag.
type structField struct {
	reflect.StructField
	value reflect.Value
}

func ebpfFields(structVal reflect.Value, visited map[reflect.Type]bool) (*set.Set[string], error) {
	if visited == nil {
		visited = make(map[reflect.Type]bool)
	}

	structType := structVal.Type()
	if structType.Kind() != reflect.Struct {
		return nil, fmt.Errorf("%s is not a struct", structType)
	}

	if visited[structType] {
		return nil, fmt.Errorf("recursion on type %s", structType)
	}

	keep := set.NewSet[string]()
	for i := 0; i < structType.NumField(); i++ {
		field := structField{structType.Field(i), structVal.Field(i)}

		// If the field is tagged, gather it and move on.
		name := field.Tag.Get("ebpf")
		if name != "" {
			keep.Insert(name)
			continue
		}

		// If the field does not have an ebpf tag, but is a struct or a pointer
		// to a struct, attempt to gather its fields as well.
		var v reflect.Value
		switch field.Type.Kind() {
		case reflect.Pointer:

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Remove the 'ebpf' tag from the field that creates the cycle.
  2. Restructure types so tagged fields form a DAG (no cycles).
  3. If recursion is intentional, hoist the nested struct out and call Fields on it separately.

Example fix

// before
type Node struct {
    Next *Node `ebpf:"next"`
}

// after
type Node struct {
    Next *Node // no ebpf tag: breaks the cycle
}
Defensive patterns

Strategy: validation

Validate before calling

// detect cycles in tagged fields before calling Fields
func hasSelfReference(t reflect.Type, seen map[reflect.Type]bool) bool {
    if seen[t] { return true }
    seen[t] = true
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        if _, ok := f.Tag.Lookup("ebpf"); ok && f.Type.Kind() == reflect.Ptr {
            if hasSelfReference(f.Type.Elem(), seen) { return true }
        }
    }
    return false
}

Type guard

func isAcyclicTaggedStruct(v any) bool {
    t := reflect.TypeOf(v)
    if t.Kind() == reflect.Ptr { t = t.Elem() }
    return !hasSelfReference(t, map[reflect.Type]bool{})
}

Try / catch

names, err := analyze.Fields(&cfg)
if err != nil {
    if strings.Contains(err.Error(), "recursion on type") {
        return fmt.Errorf("ebpf-tagged struct cycle detected: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: A struct with an 'ebpf'-tagged pointer field pointing to its own type (or a cycle A->B->A); mutual recursion between two tagged struct types.

Common situations: Linked-list or tree-like self-referential types accidentally carrying ebpf tags; refactoring that introduces a cycle in tagged nested structs.

Related errors


AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31). Data as JSON: /api/errors/ac8d9d307a6bc085. Report an issue: GitHub.