{"record":{"id":"d1cfe69b286eb95d","repo":"cilium/cilium","slug":"pruning-unused-maps-w","errorCode":null,"errorMessage":"pruning unused maps: %w","messagePattern":"pruning unused maps: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/bpf/collection.go","lineNumber":256,"sourceCode":"\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)\n\n\tif err := patchPrograms(spec, opts.ProgramPatches); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"applying program patches: %w\", err)\n\t}\n","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/cilium/cilium/blob/ac7b90affa4baf0642e6685319d56907b3a73a6d/pkg/bpf/collection.go#L238-L274","documentation":"LoadCollection wraps failures from removeUnusedMaps, which prunes MapSpecs not referenced by any live (reachable) code path, poisoning map loads on dead paths so lazy loading doesn't pull them in. It fails when reachability information is missing (nil, or no entry for a program in the spec). This is the library's dead-map elimination step; a failure means the pruning pass could not run safely.","triggerScenarios":"Calling LoadCollection/LoadAndAssign where the internal reachables map is nil, or where removeUnusedMaps iterates spec.Programs and finds a program name with no reachability entry ('missing reachability information for program %s') — e.g. programs added to the spec after computeReachability ran, or a spec mutated between steps.","commonSituations":"Custom code that adds or replaces programs in the CollectionSpec between LoadCollection's analysis steps (only via internal hooks) or reuses/mutates a spec concurrently while LoadCollection is running on its copy; embedding LoadCollection in a fork with modified pipeline stages; races where the caller mutates spec during load (note: LoadCollection copies the spec, so concurrent mutation of the original is safe — errors arise when the copied spec's programs diverge from reachability results through modified pipeline code).","solutions":["Update to the latest Cilium version — this is usually an internal inconsistency bug; report it with the full wrapped error chain if reproducible.","Do not mutate the CollectionSpec (add/remove/rename programs) concurrently with or via hooks into LoadCollection.","If running a fork with custom pipeline steps, ensure any spec modification happens before computeReachability and that reach is recomputed afterwards.","Verify opts.Keep entries reference programs/maps that actually exist in the spec."],"exampleFix":"// before: forked pipeline mutates programs after reachability\nreach, _ := computeReachability(spec)\nspec.Programs[\"extra_prog\"] = customProg // not in reach\nremoveUnusedMaps(spec, fixed, reach, logger) // pruning unused maps: missing reachability information for program extra_prog\n\n// after: modify spec first, then compute reachability once\nspec.Programs[\"extra_prog\"] = customProg\nreach, err := computeReachability(spec)\nif err != nil { return err }\nif err := removeUnusedMaps(spec, fixed, reach, logger); err != nil { return err }","handlingStrategy":"try-catch","validationCode":"func specProgramsUnchanged(specCopy, orig *ebpf.CollectionSpec) bool {\n\treturn len(specCopy.Programs) == len(orig.Programs)\n}\n// sanity check before load: analysis assumes programs present when computeReachability ran","typeGuard":"func reachabilityCovers(spec *ebpf.CollectionSpec, reach map[string]any) bool {\n\tfor name := range spec.Programs {\n\t\tif _, ok := reach[name]; !ok {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}","tryCatchPattern":"if _, _, err := bpf.LoadCollection(logger, spec, opts); err != nil {\n\tif strings.Contains(err.Error(), \"pruning unused maps\") {\n\t\treturn fmt.Errorf(\"internal loader inconsistency (missing reachability info); upgrade cilium or report bug: %w\", err)\n\t}\n\treturn err\n}","preventionTips":["Treat CollectionSpec as read-only after handing it to LoadCollection; the function copies it, so don't mutate concurrently.","Don't fork the load pipeline; insert spec modifications only before calling LoadCollection.","Keep Cilium updated — these errors typically indicate internal bugs fixed upstream.","Capture the full wrapped error chain (%+v) when filing issues; it names the offending program."],"tags":["ebpf","bpf-loader","dead-code-elimination","cilium"],"backgroundTag":"bpf-unused-map-pruning-failed","analyzedSha":"ac7b90affa4baf0642e6685319d56907b3a73a6d","analyzedAt":"2026-08-31T18:27:15.868Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}