{"record":{"id":"b4802956dc1e9469","repo":"gotify/server","slug":"config-is-already-enabled-disabled","errorCode":null,"errorMessage":"config is already enabled/disabled","messagePattern":"config is already enabled/disabled","errorType":"exception","errorClass":"ErrAlreadyEnabledOrDisabled","httpStatus":null,"severity":"error","filePath":"plugin/manager.go","lineNumber":104,"sourceCode":"\tif err := manager.loadPlugins(directory); err != nil {\n\t\treturn nil, err\n\t}\n\n\tusers, err := manager.db.GetUsers()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, user := range users {\n\t\tif err := manager.initializeForUser(*user); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn manager, nil\n}\n\n// ErrAlreadyEnabledOrDisabled is returned on SetPluginEnabled call when a plugin is already enabled or disabled.\nvar ErrAlreadyEnabledOrDisabled = errors.New(\"config is already enabled/disabled\")\n\n// SetPluginEnabled sets the plugins enabled state.\nfunc (m *Manager) SetPluginEnabled(pluginID uint, enabled bool) error {\n\tinstance, err := m.Instance(pluginID)\n\tif err != nil {\n\t\treturn errors.New(\"instance not found\")\n\t}\n\tconf, err := m.db.GetPluginConfByID(pluginID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif conf.Enabled == enabled {\n\t\treturn ErrAlreadyEnabledOrDisabled\n\t}\n\n\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/plugin/manager.go#L86-L122","documentation":"Sentinel error ErrAlreadyEnabledOrDisabled returned by Manager.SetPluginEnabled when the requested state change is a no-op: the plugin conf is already in the requested enabled/disabled state. It is a generic guard used by the API handlers at api/plugin.go:140 and api/plugin.go:198 to answer HTTP 400 instead of 500.","triggerScenarios":"Thrown at plugin/manager.go:104 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Check current plugin state before calling SetPluginEnabled and skip redundant calls","In the API client, treat this sentinel (errors.Is / == comparison) as an idempotent success rather than a failure","Return 200/304-style response in the handler when the desired state already holds"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}