fatedier/frp · warning · ErrAlreadyExists
%w: %s %q
Error message
%w: %s %q
What it means
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.
Source
Thrown at pkg/config/source/store.go:211
func visitorStoreEntries(s *StoreSource) map[string]v1.VisitorConfigurer {
return s.visitors
}
// Store entry helpers share mutation, persistence, and rollback for proxy and visitor maps.
// T is intentionally limited by callers to v1.ProxyConfigurer or v1.VisitorConfigurer.
func addStoreEntry[T any](
s *StoreSource,
entriesFn func(*StoreSource) map[string]T,
kind string,
name string,
value T,
) error {
s.mu.Lock()
defer s.mu.Unlock()
entries := entriesFn(s)
if _, exists := entries[name]; exists {
return fmt.Errorf("%w: %s %q", ErrAlreadyExists, kind, name)
}
entries[name] = value
return s.persistOrRollbackUnlocked(func() {
delete(entries, name)
})
}
func updateStoreEntry[T any](
s *StoreSource,
entriesFn func(*StoreSource) map[string]T,
kind string,
name string,
value T,
) error {
s.mu.Lock()
defer s.mu.Unlock()
View on GitHub (pinned to 6c8a8d0a97)
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
Example fix
// before
err := store.AddProxy(proxyCfg) // "already exists: proxy \"web\"" on restart
// after
err := store.AddProxy(proxyCfg)
if errors.Is(err, source.ErrAlreadyExists) {
err = store.UpdateProxy(proxyCfg)
} Defensive patterns
Strategy: try-catch
Validate before calling
// preferred: attempt the add and branch on the sentinel // (checking existence first would race with concurrent writers)
Type guard
func isAlreadyExists(err error) bool {
return errors.Is(err, source.ErrAlreadyExists)
} Try / catch
if err := store.AddProxy(cfg); err != nil {
if errors.Is(err, source.ErrAlreadyExists) {
if err := store.UpdateProxy(cfg); err != nil {
return err
}
} else {
return err
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ErrInvalidArgument
- ErrConflict
- ErrAlreadyExists
- exec configuration is required when type is 'exec'
- file path cannot be empty
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/5b4af2d3d48d899e.
Report an issue: GitHub.