fatedier/frp · error · configmgmt.ErrConflict

conflict: %v

Error message

conflict: %v

What it means

Returned by CreateStoreProxy when storeSource.AddProxy reports source.ErrAlreadyExists, meaning a proxy with the same name is already persisted in the store. Wrapped with configmgmt.ErrConflict and surfaced as HTTP 409. Creation is rejected without touching the existing entry.

Source

Thrown at client/config_manager.go:187

	}

	cfg := storeSource.GetProxy(name)
	if cfg == nil {
		return nil, fmt.Errorf("%w: proxy %q", configmgmt.ErrNotFound, name)
	}
	return cfg, nil
}

func (m *serviceConfigManager) CreateStoreProxy(cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
	if err := m.validateStoreProxyConfigurer(cfg); err != nil {
		return nil, fmt.Errorf("%w: validation error: %v", configmgmt.ErrInvalidArgument, err)
	}

	name := cfg.GetBaseConfig().Name
	persisted, err := m.withStoreProxyMutationAndReload(name, func(storeSource *source.StoreSource) error {
		if err := storeSource.AddProxy(cfg); err != nil {
			if errors.Is(err, source.ErrAlreadyExists) {
				return fmt.Errorf("%w: %v", configmgmt.ErrConflict, err)
			}
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	log.Infof("store: created proxy %q", name)
	return persisted, nil
}

func (m *serviceConfigManager) UpdateStoreProxy(name string, cfg v1.ProxyConfigurer) (v1.ProxyConfigurer, error) {
	if name == "" {
		return nil, fmt.Errorf("%w: proxy name is required", configmgmt.ErrInvalidArgument)
	}
	if cfg == nil {
		return nil, fmt.Errorf("%w: invalid proxy config: type is required", configmgmt.ErrInvalidArgument)

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. If you mean to replace it, use PUT /api/store/proxies/{name} (UpdateStoreProxy) instead of POST.
  2. If the duplicate is accidental, pick a different name or delete the existing one with DELETE /api/store/proxies/{name} first.
  3. Make provisioning scripts check GET /api/store/proxies/{name} (expect 404) before POSTing, or treat 409 as 'already done'.

Example fix

# before
curl -X POST http://127.0.0.1:7400/api/store/proxies -d '{"type":"tcp","name":"ssh","localPort":22,"remotePort":6022}'
# 409 conflict: proxy ssh already exists

# after
curl -X PUT http://127.0.0.1:7400/api/store/proxies/ssh -d '{"type":"tcp","name":"ssh","localPort":22,"remotePort":6022}'
Defensive patterns

Strategy: try-catch

Validate before calling

// Optional pre-check: only POST when GET returns 404.
if _, err := mgr.GetStoreProxy(name); err == nil {
    // exists -> update instead
}

Try / catch

if _, err := mgr.CreateStoreProxy(cfg); err != nil {
    if errors.Is(err, configmgmt.ErrConflict) {
        // already provisioned: switch to UpdateStoreProxy or treat as done
    }
}

Prevention

When it happens

Trigger: POST /api/store/proxies with a name that already exists in the store; re-running an idempotent provisioning script that always POSTs the same proxy; racing two concurrent creates of the same name.

Common situations: Infrastructure tooling (Terraform/Ansible) re-applying the same proxy definition; retry of a request whose response was lost, so the first attempt actually succeeded; migrating a file-defined proxy to the store without deleting the original.

Related errors


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