{"record":{"id":"4cc04221f482dc64","repo":"fatedier/frp","slug":"feature-gate-q-with-different-spec-already-exists","errorCode":null,"errorMessage":"feature gate %q with different spec already exists: %v","messagePattern":"feature gate %q with different spec already exists: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/policy/featuregate/feature_gate.go","lineNumber":150,"sourceCode":"// Add adds features to the feature gate\nfunc (f *featureGate) Add(features map[Feature]FeatureSpec) error {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\n\tif f.closed {\n\t\treturn fmt.Errorf(\"cannot add feature gates after the feature gate is closed\")\n\t}\n\n\t// Copy existing state\n\tknown := maps.Clone(f.known.Load().(map[Feature]FeatureSpec))\n\n\t// Add new features\n\tfor name, spec := range features {\n\t\tif existingSpec, found := known[name]; found {\n\t\t\tif existingSpec == spec {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"feature gate %q with different spec already exists: %v\", name, existingSpec)\n\t\t}\n\t\tknown[name] = spec\n\t}\n\n\t// Persist changes\n\tf.known.Store(known)\n\n\treturn nil\n}\n\n// String returns a string containing all enabled feature gates, formatted as \"key1=value1,key2=value2,...\"\nfunc (f *featureGate) String() string {\n\tenabled := f.enabled.Load().(map[Feature]bool)\n\tpairs := make([]string, 0, len(enabled))\n\tfor k, v := range enabled {\n\t\tpairs = append(pairs, fmt.Sprintf(\"%s=%t\", k, v))\n\t}\n\tsort.Strings(pairs)","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/policy/featuregate/feature_gate.go#L132-L168","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nfg.Add(map[Feature]FeatureSpec{\"F\": {Default: false}})\nfg.Add(map[Feature]FeatureSpec{\"F\": {Default: true}}) // error: different spec\n\n// after — one canonical spec\nvar fSpec = FeatureSpec{DefaultToEnable: true}\nfg.Add(map[Feature]FeatureSpec{\"F\": fSpec})\nfg.Add(map[Feature]FeatureSpec{\"F\": fSpec}) // idempotent, ok","handlingStrategy":"validation","validationCode":"// Register from a single shared spec map only\nvar allGates = map[Feature]FeatureSpec{\n    \"MyFeature\": {DefaultToEnable: false, LockToDefault: false},\n}\nif err := fg.Add(allGates); err != nil { return err }","typeGuard":null,"tryCatchPattern":"if err := fg.Add(specs); err != nil && strings.Contains(err.Error(), \"different spec already exists\") {\n    // two owners disagree; reconcile specs to a single source of truth\n    return err\n}","preventionTips":["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"],"tags":["feature-flags","configuration","go"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}