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
- Remove the 'ebpf' tag from the field that creates the cycle.
- Restructure types so tagged fields form a DAG (no cycles).
- 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
- Never put an 'ebpf' tag on a field whose type (transitively) contains itself.
- Keep tagged nested structs as a tree/DAG.
- Add a test that calls Fields on all tagged types in CI.
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
- nil interface passed for type %s
- type %s is not a struct
- %T is not a pointer to struct
- nil pointer to %T
- %s is not a struct
AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31).
Data as JSON: /api/errors/ac8d9d307a6bc085.
Report an issue: GitHub.