etcd-io/etcd · error

feature %q is not registered in FeatureGate %q

Error message

feature %q is not registered in FeatureGate %q

What it means

This panic is thrown by featuregate's Enabled() when the requested Feature key is present in neither the runtime 'enabled' map nor the registered 'known' specs map of that FeatureGate instance. It is an intentional fail-fast: silently returning false for an unknown gate would hide configuration mistakes. The gate's specs are populated via Add/AddMap/SetFromMap before Enabled is consulted, so an unknown key means the caller and the registration code disagree.

Source

Thrown at pkg/featuregate/feature_gate.go:342

}

// GetAll returns a copy of the map of known feature names to feature specs.
func (f *featureGate) GetAll() map[Feature]FeatureSpec {
	retval := map[Feature]FeatureSpec{}
	maps.Copy(retval, f.known.Load().(map[Feature]FeatureSpec))
	return retval
}

// Enabled returns true if the key is enabled.  If the key is not known, this call will panic.
func (f *featureGate) Enabled(key Feature) bool {
	if v, ok := f.enabled.Load().(map[Feature]bool)[key]; ok {
		return v
	}
	if v, ok := f.known.Load().(map[Feature]FeatureSpec)[key]; ok {
		return v.Default
	}

	panic(fmt.Errorf("feature %q is not registered in FeatureGate %q", key, f.featureGateName))
}

// AddFlag adds a flag for setting global feature gates to the specified FlagSet.
func (f *featureGate) AddFlag(fs *flag.FlagSet, flagName string) {
	if flagName == "" {
		flagName = defaultFlagName
	}
	f.lock.Lock()
	// TODO(mtaufen): Shouldn't we just close it on the first Set/SetFromMap instead?
	// Not all components expose a feature gates flag using this AddFlag method, and
	// in the future, all components will completely stop exposing a feature gates flag,
	// in favor of componentconfig.
	f.closed = true
	f.lock.Unlock()

	known := f.KnownFeatures()
	fs.Var(f, flagName, ""+
		"A set of key=value pairs that describe feature gates for alpha/experimental features. "+

View on GitHub (pinned to f744d457f4)

Solutions

  1. Find where the FeatureGate is populated (embed/config.go or wherever Add/AddMap/SetFromMap is called) and add the missing feature with featuregate.Add(FeatureName, FeatureSpec{Default: false, PreRelease: ...}) before any Enabled call.
  2. If the feature exists upstream, align module versions (go.mod / go.work) so the registering code and the Enabled caller come from the same etcd release; renamed/graduated gates must be referenced by their current constant.
  3. List what the gate actually knows with f.KnownFeatures() (or inspect the AddMap literal) to confirm the exact spelling of the key.
  4. If you only want an optional check, test membership of KnownFeatures() output instead of calling Enabled for possibly-absent keys.

Example fix

// before
fg := featuregate.New("my-gate")
// ... component later calls:
if fg.Enabled(featuregate.Feature("MyFeature")) { ... } // panics: never registered

// after
fg := featuregate.New("my-gate")
err := fg.Add(map[featuregate.Feature]featuregate.FeatureSpec{
	"MyFeature": {Default: false, PreRelease: featuregate.Alpha},
})
if err != nil {
	return err
}
// now Enabled("MyFeature") returns the default instead of panicking
Defensive patterns

Strategy: validation

Validate before calling

// Before calling Enabled, confirm the feature is registered:
func featureRegistered(fg featuregate.FeatureGate, key featuregate.Feature) bool {
	for _, k := range fg.KnownFeatures() { // KnownFeatures includes lock/alpha markers; check raw membership instead
		if strings.TrimPrefix(k, "lock_") == string(key) || k == string(key) {
			return true
		}
	}
	return false
}

if !featureRegistered(fg, "MyFeature") {
	// register it or skip the check
}

Prevention

When it happens

Trigger: Calling featureGate.Enabled(key) where key was never registered with featureGate.Add(...) or AddMap(...). Typical in etcd's embed server path: cfg.AddFeatureGate / options parsing register gates first, then component code calls Enabled("SomeFeature"); if the component references a feature constant introduced (or renamed) in a different version than the one that registered the gates, the panic fires.

Common situations: Version skew between etcd modules (e.g. server registered gates at v3.5 while shared code expects a v3.6 gate name); typos when passing a feature as a raw string from flags/env; calling Enabled on a freshly created gate (featuregate.New) before any Add; copy-pasting a feature name that was removed (graduated/removed features disappear from known maps after cleanup).

Related errors


AI-assisted analysis of etcd-io/etcd@f744d457f4 (2026-08-15). Data as JSON: /api/errors/d33d9882d2069cb0. Report an issue: GitHub.