{"record":{"id":"5b4af2d3d48d899e","repo":"fatedier/frp","slug":"w-s-q","errorCode":null,"errorMessage":"%w: %s %q","messagePattern":"%w: (.+?) %q","errorType":"http","errorClass":"ErrAlreadyExists","httpStatus":409,"severity":"warning","filePath":"pkg/config/source/store.go","lineNumber":211,"sourceCode":"func visitorStoreEntries(s *StoreSource) map[string]v1.VisitorConfigurer {\n\treturn s.visitors\n}\n\n// Store entry helpers share mutation, persistence, and rollback for proxy and visitor maps.\n// T is intentionally limited by callers to v1.ProxyConfigurer or v1.VisitorConfigurer.\nfunc addStoreEntry[T any](\n\ts *StoreSource,\n\tentriesFn func(*StoreSource) map[string]T,\n\tkind string,\n\tname string,\n\tvalue T,\n) error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\tentries := entriesFn(s)\n\tif _, exists := entries[name]; exists {\n\t\treturn fmt.Errorf(\"%w: %s %q\", ErrAlreadyExists, kind, name)\n\t}\n\n\tentries[name] = value\n\treturn s.persistOrRollbackUnlocked(func() {\n\t\tdelete(entries, name)\n\t})\n}\n\nfunc updateStoreEntry[T any](\n\ts *StoreSource,\n\tentriesFn func(*StoreSource) map[string]T,\n\tkind string,\n\tname string,\n\tvalue T,\n) error {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/source/store.go#L193-L229","documentation":"AddProxy or AddVisitor rejected the operation because an entry with the same name already exists in the store. The error wraps the exported sentinel source.ErrAlreadyExists, so callers can distinguish a name conflict from persistence or validation failures. No state changes: the existing entry is untouched.","triggerScenarios":"store.AddProxy(cfg) where a proxy with the same cfg.GetBaseConfig().Name was already added (in this session or loaded from the store file); re-running an initialization routine that adds the same proxies twice; two components racing to register the same name.","commonSituations":"Idempotency-unaware startup code that re-adds proxies on every restart; loading a store file that already contains the proxy and then adding it again programmatically; retry loops that treat a partial failure as full failure.","solutions":["If the intent is to overwrite, call UpdateProxy/UpdateVisitor instead of Add","Otherwise pick a unique name before adding","Make startup idempotent: treat ErrAlreadyExists as success when re-applying desired state","Remove the stale entry first with RemoveProxy/RemoveVisitor if it should no longer exist"],"exampleFix":"// before\nerr := store.AddProxy(proxyCfg) // \"already exists: proxy \\\"web\\\"\" on restart\n\n// after\nerr := store.AddProxy(proxyCfg)\nif errors.Is(err, source.ErrAlreadyExists) {\n    err = store.UpdateProxy(proxyCfg)\n}","handlingStrategy":"try-catch","validationCode":"// preferred: attempt the add and branch on the sentinel\n// (checking existence first would race with concurrent writers)","typeGuard":"func isAlreadyExists(err error) bool {\n\treturn errors.Is(err, source.ErrAlreadyExists)\n}","tryCatchPattern":"if err := store.AddProxy(cfg); err != nil {\n\tif errors.Is(err, source.ErrAlreadyExists) {\n\t\tif err := store.UpdateProxy(cfg); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\treturn err\n\t}\n}","preventionTips":["Make registration code idempotent: upsert on ErrAlreadyExists instead of failing","Derive proxy/visitor names from stable identifiers so restarts reuse the same names","Never ignore ErrAlreadyExists silently in a loop — log it to detect unintended double-registration"],"tags":["conflict","duplicate","validation","idempotency","go"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}