fatedier/frp · error · configmgmt.ErrNotFound

not found: visitor %q

Error message

not found: visitor %q

What it means

Returned by GetStoreVisitor when the store source holds no visitor with the requested name (storeSource.GetVisitor(name) returned nil). Wrapped as configmgmt.ErrNotFound and surfaced as HTTP 404. Visitors (stcp/sudp/xtcp companions) are stored separately from proxies.

Source

Thrown at client/config_manager.go:273

	if err != nil {
		return nil, err
	}
	return storeSource.GetAllVisitors()
}

func (m *serviceConfigManager) GetStoreVisitor(name string) (v1.VisitorConfigurer, error) {
	if name == "" {
		return nil, fmt.Errorf("%w: visitor name is required", configmgmt.ErrInvalidArgument)
	}

	storeSource, err := m.storeSourceOrError()
	if err != nil {
		return nil, err
	}

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

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

	name := cfg.GetBaseConfig().Name
	persisted, err := m.withStoreVisitorMutationAndReload(name, func(storeSource *source.StoreSource) error {
		if err := storeSource.AddVisitor(cfg); err != nil {
			if errors.Is(err, source.ErrAlreadyExists) {
				return fmt.Errorf("%w: %v", configmgmt.ErrConflict, err)
			}
			return err
		}
		return nil

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. List visitors with GET /api/store/visitors and use the exact name.
  2. Create the visitor with POST /api/store/visitors if it should exist.
  3. Check you targeted the correct frpc instance (the side that runs the visitor).

Example fix

# before
curl .../api/store/visitors/db   # 404 not found: visitor "db"

# after
curl .../api/store/visitors | jq '.[].name'
curl .../api/store/visitors/db-visitor
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm via list before GET by name.
func visitorExists(base, name string) bool {
    resp, _ := http.Get(base + "/api/store/visitors")
    defer resp.Body.Close()
    var list []struct{ Name string }
    json.NewDecoder(resp.Body).Decode(&list)
    for _, v := range list { if v.Name == name { return true } }
    return false
}

Try / catch

cfg, err := mgr.GetStoreVisitor(name)
if err != nil {
    if errors.Is(err, configmgmt.ErrNotFound) {
        // create it or report cleanly; check you didn't pass the proxy name
    }
}

Prevention

When it happens

Trigger: GET /api/store/visitors/{name} for a visitor that was never created, was deleted, or whose name is misspelled; querying the proxy name instead of the visitor name.

Common situations: Confusing the proxy name (e.g. 'db') with its visitor name (e.g. 'db-visitor'); assuming visitors defined in the static TOML file appear in the store; stale documentation naming.

Related errors


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