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

  1. If the intent is to overwrite, call UpdateProxy/UpdateVisitor instead of Add
  2. Otherwise pick a unique name before adding
  3. Make startup idempotent: treat ErrAlreadyExists as success when re-applying desired state
  4. 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

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


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/5b4af2d3d48d899e. Report an issue: GitHub.