cilium/cilium · error
registry has already been started
Error message
registry has already been started
What it means
ErrStarted is a sentinel error of the pkg/maps/registry MapRegistry. The registry is a Hive cell holding [ebpf.MapSpec]s for all pinned datapath maps; after Start() it becomes read-only because patches must be applied before map pinning. It is returned by start (double start), Modify, and TestMapRegistry to signal that lifecycle transition is no longer permitted.
Source
Thrown at pkg/maps/registry/registry.go:22
package registry
import (
_ "embed"
"errors"
"fmt"
"log/slog"
"github.com/cilium/ebpf"
_ "github.com/cilium/hive/cell"
"github.com/cilium/cilium/pkg/datapath/maps"
"github.com/cilium/cilium/pkg/lock"
"github.com/cilium/cilium/pkg/logging/logfields"
)
var (
ErrStarted = errors.New("registry has already been started")
ErrNotStarted = errors.New("registry has not yet been started")
ErrMapNotFound = errors.New("MapSpec not found")
)
// MapRegistry contains [ebpf.MapSpec]s for all pinned maps in the datapath.
//
// The registry allows Cells to provide [MapSpecPatch]es during Hive
// construction, e.g. for changing MaxEntries of a map based on configuration
// parameters. Only select fields can be modified, see [MapSpecPatch] for
// details.
//
// Once the registry has been started, MapSpecs can only be retrieved but
// not modified. Any packages which need to create maps at runtime should
// obtain the MapSpecs from this registry.
type MapRegistry struct {
l *slog.Logger
mu lock.MutexView on GitHub (pinned to ac7b90affa)
Solutions
- Reorder code so Start() is invoked exactly once, via Hive rather than manually.
- Move all Modify()/TestMapRegistry() calls before registry startup (during Hive construction).
- Use errors.Is(err, registry.ErrStarted) to detect and skip an idempotent second start.
- Refactor the component to depend on the already-started registry instead of starting its own.
Example fix
// before
registry.Start()
...
registry.Start() // panics path with ErrStarted
// after
if err := registry.Start(); err != nil {
if !errors.Is(err, registry.ErrStarted) { return err } // already started: ok, idempotent
} Defensive patterns
Strategy: try-catch
Validate before calling
// guard before modifying
if registryStarted(reg) {
return fmt.Errorf("cannot modify registry after start")
} Type guard
func registryStarted(r *registry.MapRegistry) bool {
// exposed state check: attempt an operation that is only valid pre-start
return r.GetPatch("__probe__") == nil // if lookup works, registry is started
} Try / catch
if err := reg.Modify(patch); err != nil {
if errors.Is(err, registry.ErrStarted) {
// registry already started: move modification to pre-start phase or ignore
return nil
}
return err
} Prevention
- Only modify the registry during Hive construction, never in Start/PostStart hooks
- Start the registry exclusively through the Hive lifecycle to avoid double start
- Use errors.Is against registry.ErrStarted for idempotent start paths
When it happens
Trigger: Calling Start() (or the Hive lifecycle start) twice on the same MapRegistry; calling Modify() or TestMapRegistry() after the registry has been started.
Common situations: Duplicated cell registration or a module invoking the registry's lifecycle hook manually in addition to Hive; tests or tooling that fetch/patch maps after agent startup has completed.
Related errors
- registry has not yet been started
- MapSpec not found
- assigning eBPF objects to %T: %w
- checking for unspecified programs: %w
- replacing maps from registry: %w
AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31).
Data as JSON: /api/errors/50f43e1aa4262745.
Report an issue: GitHub.