cilium/cilium · error

resolving tail calls: %w

Error message

resolving tail calls: %w

What it means

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.

Source

Thrown at pkg/bpf/collection.go:251

	if err := renameMaps(spec, opts.MapRenames); err != nil {
		return nil, nil, fmt.Errorf("renaming maps: %w", err)
	}

	if err := applyConstants(spec, opts.Constants); err != nil {
		return nil, nil, fmt.Errorf("applying variable overrides: %w", err)
	}

	reach, err := computeReachability(spec)
	if err != nil {
		return nil, nil, fmt.Errorf("computing reachability: %w", err)
	}

	if err := removeUnusedTailcalls(spec, reach, logger); err != nil {
		return nil, nil, fmt.Errorf("removing unused tail calls: %w", err)
	}

	if err := resolveTailCalls(spec); err != nil {
		return nil, nil, fmt.Errorf("resolving tail calls: %w", err)
	}

	fixed := fixedResources(spec, opts.Keep)
	if err := removeUnusedMaps(spec, fixed, reach, logger); err != nil {
		return nil, nil, fmt.Errorf("pruning unused maps: %w", err)
	}

	if err := dumpConstants(spec, opts); err != nil {
		return nil, nil, fmt.Errorf("writing constants: %w", err)
	}

	if err := modifyAuxData(spec); err != nil {
		return nil, nil, fmt.Errorf("loading auxiliary data: %w", err)
	}

	// Find and strip all CILIUM_PIN_REPLACE pinning flags before creating the
	// Collection. ebpf-go will reject maps with pins it doesn't recognize.
	toReplace := consumePinReplace(spec)

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Read the wrapped message: fix duplicate slot by giving each tail-call program a unique tail:N value in the C source.
  2. Ensure the map named cilium_calls (post-rename) is declared as BPF_MAP_TYPE_PROG_ARRAY.
  3. Do not merge objects that both declare the same tail-call slots; build one collection or adjust slot numbers.
  4. If you renamed maps with opts.MapRenames, confirm the rename target still points at the ProgramArray.

Example fix

// C before: two programs share slot 3
__declare_tail(3) int handle_ipv6(struct __ctx_buff *ctx) { ... }
__declare_tail(3) int handle_ipv4(struct __ctx_buff *ctx) { ... }
// resolving tail calls: duplicate tail call slot 3

// after: unique slots
__declare_tail(3) int handle_ipv6(struct __ctx_buff *ctx) { ... }
__declare_tail(4) int handle_ipv4(struct __ctx_buff *ctx) { ... }
Defensive patterns

Strategy: validation

Validate before calling

func validateCallsMap(spec *ebpf.CollectionSpec) error {
	ms, ok := spec.Maps["cilium_calls"]
	if !ok {
		return nil // resolveTailCalls no-ops when the map is absent
	}
	if ms.Type != ebpf.ProgramArray {
		return fmt.Errorf("cilium_calls must be a program array, got %s", ms.Type)
	}
	return nil
}

Type guard

func isProgramArray(spec *ebpf.CollectionSpec, name string) bool {
	m, ok := spec.Maps[name]
	return ok && m.Type == ebpf.ProgramArray
}

Try / catch

if _, _, err := bpf.LoadCollection(logger, spec, opts); err != nil {
	if strings.Contains(err.Error(), "resolving tail calls") {
		if strings.Contains(err.Error(), "duplicate tail call slot") {
			return fmt.Errorf("two programs claim the same tail-call slot; assign unique tail:N values in C source: %w", err)
		}
		return fmt.Errorf("cilium_calls map invalid or tail slot unresolved: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: 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'.

Common situations: 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.

Related errors


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