{"record":{"id":"34fc49e838e160d0","repo":"cilium/cilium","slug":"resolving-tail-calls-w","errorCode":null,"errorMessage":"resolving tail calls: %w","messagePattern":"resolving tail calls: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/bpf/collection.go","lineNumber":251,"sourceCode":"\tif err := renameMaps(spec, opts.MapRenames); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"renaming maps: %w\", err)\n\t}\n\n\tif err := applyConstants(spec, opts.Constants); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"applying variable overrides: %w\", err)\n\t}\n\n\treach, err := computeReachability(spec)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"computing reachability: %w\", err)\n\t}\n\n\tif err := removeUnusedTailcalls(spec, reach, logger); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"removing unused tail calls: %w\", err)\n\t}\n\n\tif err := resolveTailCalls(spec); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"resolving tail calls: %w\", err)\n\t}\n\n\tfixed := fixedResources(spec, opts.Keep)\n\tif err := removeUnusedMaps(spec, fixed, reach, logger); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"pruning unused maps: %w\", err)\n\t}\n\n\tif err := dumpConstants(spec, opts); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"writing constants: %w\", err)\n\t}\n\n\tif err := modifyAuxData(spec); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"loading auxiliary data: %w\", err)\n\t}\n\n\t// Find and strip all CILIUM_PIN_REPLACE pinning flags before creating the\n\t// Collection. ebpf-go will reject maps with pins it doesn't recognize.\n\ttoReplace := consumePinReplace(spec)","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/cilium/cilium/blob/ac7b90affa4baf0642e6685319d56907b3a73a6d/pkg/bpf/collection.go#L233-L269","documentation":"LoadCollection wraps failures from resolveTailCalls, which populates the cilium_calls program array with programs annotated __declare_tail. Failures occur when the cilium_calls map is not a ProgramArray, a tail-call program has no resolvable slot, or two programs claim the same tail-call slot (duplicate slot). This guarantees the kernel program array maps each slot exactly once before the collection is created.","triggerScenarios":"Calling LoadCollection/LoadAndAssign where spec.Maps['cilium_calls'] exists but has a type other than ebpf.ProgramArray; a program marked IsTailCall returns an error from tailCallSlot ('no tail call slot'/'no function metadata'); or two programs resolve to the same slot index producing 'duplicate tail call slot %d'.","commonSituations":"Renaming or replacing the cilium_calls map via MapRenames so it loses its ProgramArray type; compiling two C files with colliding tail:N slot values into one collection; hand-built specs that include a calls map of the wrong type; including the same object twice so duplicate slots appear.","solutions":["Read the wrapped message: fix duplicate slot by giving each tail-call program a unique tail:N value in the C source.","Ensure the map named cilium_calls (post-rename) is declared as BPF_MAP_TYPE_PROG_ARRAY.","Do not merge objects that both declare the same tail-call slots; build one collection or adjust slot numbers.","If you renamed maps with opts.MapRenames, confirm the rename target still points at the ProgramArray."],"exampleFix":"// C before: two programs share slot 3\n__declare_tail(3) int handle_ipv6(struct __ctx_buff *ctx) { ... }\n__declare_tail(3) int handle_ipv4(struct __ctx_buff *ctx) { ... }\n// resolving tail calls: duplicate tail call slot 3\n\n// after: unique slots\n__declare_tail(3) int handle_ipv6(struct __ctx_buff *ctx) { ... }\n__declare_tail(4) int handle_ipv4(struct __ctx_buff *ctx) { ... }","handlingStrategy":"validation","validationCode":"func validateCallsMap(spec *ebpf.CollectionSpec) error {\n\tms, ok := spec.Maps[\"cilium_calls\"]\n\tif !ok {\n\t\treturn nil // resolveTailCalls no-ops when the map is absent\n\t}\n\tif ms.Type != ebpf.ProgramArray {\n\t\treturn fmt.Errorf(\"cilium_calls must be a program array, got %s\", ms.Type)\n\t}\n\treturn nil\n}","typeGuard":"func isProgramArray(spec *ebpf.CollectionSpec, name string) bool {\n\tm, ok := spec.Maps[name]\n\treturn ok && m.Type == ebpf.ProgramArray\n}","tryCatchPattern":"if _, _, err := bpf.LoadCollection(logger, spec, opts); err != nil {\n\tif strings.Contains(err.Error(), \"resolving tail calls\") {\n\t\tif strings.Contains(err.Error(), \"duplicate tail call slot\") {\n\t\t\treturn fmt.Errorf(\"two programs claim the same tail-call slot; assign unique tail:N values in C source: %w\", err)\n\t\t}\n\t\treturn fmt.Errorf(\"cilium_calls map invalid or tail slot unresolved: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Give every tail-call program a unique tail:N slot in the C source; keep slot assignments in one header.","Never rename cilium_calls to a map that isn't BPF_MAP_TYPE_PROG_ARRAY via MapRenames.","Load each compiled object exactly once per collection to avoid duplicate slot declarations.","Add a CI check that builds the collection spec and asserts one program per slot."],"tags":["ebpf","tail-call","program-array","cilium"],"backgroundTag":"bpf-duplicate-tail-call-slot","analyzedSha":"ac7b90affa4baf0642e6685319d56907b3a73a6d","analyzedAt":"2026-08-31T18:27:15.868Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}