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.Mutex

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Reorder code so Start() is invoked exactly once, via Hive rather than manually.
  2. Move all Modify()/TestMapRegistry() calls before registry startup (during Hive construction).
  3. Use errors.Is(err, registry.ErrStarted) to detect and skip an idempotent second start.
  4. 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

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


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