fatedier/frp · error
feature gate %q with different spec already exists: %v
Error message
feature gate %q with different spec already exists: %v
What it means
Add() found the same feature name already registered with a different FeatureSpec. Re-adding an identical spec is an idempotent no-op (continue), but any difference in Default/LockToDefault/PreRelease is rejected to prevent two owners from disagreeing about a gate's contract.
Source
Thrown at pkg/policy/featuregate/feature_gate.go:150
// Add adds features to the feature gate
func (f *featureGate) Add(features map[Feature]FeatureSpec) error {
f.lock.Lock()
defer f.lock.Unlock()
if f.closed {
return fmt.Errorf("cannot add feature gates after the feature gate is closed")
}
// Copy existing state
known := maps.Clone(f.known.Load().(map[Feature]FeatureSpec))
// Add new features
for name, spec := range features {
if existingSpec, found := known[name]; found {
if existingSpec == spec {
continue
}
return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec)
}
known[name] = spec
}
// Persist changes
f.known.Store(known)
return nil
}
// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,..."
func (f *featureGate) String() string {
enabled := f.enabled.Load().(map[Feature]bool)
pairs := make([]string, 0, len(enabled))
for k, v := range enabled {
pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
}
sort.Strings(pairs)View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Make all Add sites register the exact same FeatureSpec for a given name (single source of truth)
- Consolidate registration into one package/const block and have others reference it
- If the spec intentionally changed, remove the old registration or use a new feature name
Example fix
// before
fg.Add(map[Feature]FeatureSpec{"F": {Default: false}})
fg.Add(map[Feature]FeatureSpec{"F": {Default: true}}) // error: different spec
// after — one canonical spec
var fSpec = FeatureSpec{DefaultToEnable: true}
fg.Add(map[Feature]FeatureSpec{"F": fSpec})
fg.Add(map[Feature]FeatureSpec{"F": fSpec}) // idempotent, ok Defensive patterns
Strategy: validation
Validate before calling
// Register from a single shared spec map only
var allGates = map[Feature]FeatureSpec{
"MyFeature": {DefaultToEnable: false, LockToDefault: false},
}
if err := fg.Add(allGates); err != nil { return err } Try / catch
if err := fg.Add(specs); err != nil && strings.Contains(err.Error(), "different spec already exists") {
// two owners disagree; reconcile specs to a single source of truth
return err
} Prevention
- Define each gate's FeatureSpec once in a shared package and import it everywhere
- Treat duplicate-with-different-spec as a build error in CI
When it happens
Trigger: Two calls to Add with the same Feature name but differing FeatureSpec values — e.g. a module re-registers a shared gate with a different default after an upgrade.
Common situations: Two packages both try to own the same gate name with different specs; copy-pasted registration with an edited default; partially upgraded dependencies registering a revised spec for an existing gate.
Related errors
- unrecognized feature gate: %s
- cannot set feature gate %v to %v, feature is locked to %v
- ErrHealthCheckType
- unit not support
- type is required
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/4cc04221f482dc64.
Report an issue: GitHub.