{"record":{"id":"66954c59fc994292","repo":"fatedier/frp","slug":"no-sources-configured","errorCode":null,"errorMessage":"no sources configured","messagePattern":"no sources configured","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/source/aggregator.go","lineNumber":76,"sourceCode":"\nfunc (a *Aggregator) getSourcesLocked() []Source {\n\tsources := make([]Source, 0, 2)\n\tif a.configSource != nil {\n\t\tsources = append(sources, a.configSource)\n\t}\n\tif a.storeSource != nil {\n\t\tsources = append(sources, a.storeSource)\n\t}\n\treturn sources\n}\n\nfunc (a *Aggregator) Load() ([]v1.ProxyConfigurer, []v1.VisitorConfigurer, error) {\n\ta.mu.RLock()\n\tentries := a.getSourcesLocked()\n\ta.mu.RUnlock()\n\n\tif len(entries) == 0 {\n\t\treturn nil, nil, errors.New(\"no sources configured\")\n\t}\n\n\tproxyMap := make(map[string]v1.ProxyConfigurer)\n\tvisitorMap := make(map[string]v1.VisitorConfigurer)\n\n\tfor _, src := range entries {\n\t\tproxies, visitors, err := src.Load()\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"load source: %w\", err)\n\t\t}\n\t\tfor _, p := range proxies {\n\t\t\tproxyMap[p.GetBaseConfig().Name] = p\n\t\t}\n\t\tfor _, v := range visitors {\n\t\t\tvisitorMap[v.GetBaseConfig().Name] = v\n\t\t}\n\t}\n\tproxies, visitors := a.mapsToSortedSlices(proxyMap, visitorMap)","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/source/aggregator.go#L58-L94","documentation":"Aggregator.Load (pkg/config/source/aggregator.go:76) refuses to load when getSourcesLocked returns zero sources. The aggregator merges a config file source and an optional store source; a zero-value Aggregator (struct created without NewAggregator) has both fields nil, hence no sources. NewAggregator itself always installs a config source, so in practice this error means the aggregator was constructed incorrectly rather than configured empty.","triggerScenarios":"Using `var agg source.Aggregator` or `&source.Aggregator{}` directly and calling Load(); NewAggregator always returns an aggregator with a non-nil configSource, so library consumers bypassing the constructor are the real trigger.","commonSituations":"Embedding frp's config source package in another tool and instantiating the struct literal instead of NewAggregator; refactoring that drops the constructor call.","solutions":["Construct with source.NewAggregator(cfgSource) (a nil argument is fine — it creates a default ConfigSource)","Or call SetStoreSource / ensure a config source is attached before Load","Treat this error as a programming bug in the caller, not a runtime condition to retry","Add a unit test asserting Load works right after NewAggregator"],"exampleFix":"// before\nagg := &source.Aggregator{}\n_, _, err := agg.Load() // no sources configured\n\n// after\nagg := source.NewAggregator(nil)\n_, _, err := agg.Load()","handlingStrategy":"validation","validationCode":"// Never use a zero-value aggregator\nvar agg *source.Aggregator // unsafe if Load() called\n\nagg := source.NewAggregator(nil) // always has >= 1 source","typeGuard":null,"tryCatchPattern":"proxies, visitors, err := agg.Load()\nif err != nil && strings.Contains(err.Error(), \"no sources configured\") {\n    // construction bug — fix caller, do not retry\n    return fmt.Errorf(\"aggregator misuse: construct with NewAggregator: %w\", err)\n}","preventionTips":["Always construct via NewAggregator; forbid struct literals in review","Add a lint/test that Load() succeeds immediately after construction","Attach the store source with SetStoreSource as soon as it is created"],"tags":["go","config","aggregator","api-misuse"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}