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 nilView on GitHub (pinned to 6c8a8d0a97)
Solutions
- List visitors with GET /api/store/visitors and use the exact name.
- Create the visitor with POST /api/store/visitors if it should exist.
- 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
- Use exact names from GET /api/store/visitors
- Remember visitors live on the client side that dials in, not on the proxy-owning side
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
- not found: proxy %q
- not found: %v
- invalid argument: visitor name is required
- invalid argument: invalid visitor config: type is required
- invalid argument: proxy name is required
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/b6c149e7118bfc1d.
Report an issue: GitHub.