fatedier/frp · error
load source: %w
Error message
load source: %w
What it means
The aggregator source iterates every registered config source (file, store, etc.) and calls src.Load(); the first source that fails aborts aggregation with 'load source: %w'. The %w wrapping preserves the underlying error for errors.Is/As inspection, so the real cause (file parse failure, store read failure) stays chainable.
Source
Thrown at pkg/config/source/aggregator.go:85
return sources
}
func (a *Aggregator) Load() ([]v1.ProxyConfigurer, []v1.VisitorConfigurer, error) {
a.mu.RLock()
entries := a.getSourcesLocked()
a.mu.RUnlock()
if len(entries) == 0 {
return nil, nil, errors.New("no sources configured")
}
proxyMap := make(map[string]v1.ProxyConfigurer)
visitorMap := make(map[string]v1.VisitorConfigurer)
for _, src := range entries {
proxies, visitors, err := src.Load()
if err != nil {
return nil, nil, fmt.Errorf("load source: %w", err)
}
for _, p := range proxies {
proxyMap[p.GetBaseConfig().Name] = p
}
for _, v := range visitors {
visitorMap[v.GetBaseConfig().Name] = v
}
}
proxies, visitors := a.mapsToSortedSlices(proxyMap, visitorMap)
return proxies, visitors, nil
}
func (a *Aggregator) mapsToSortedSlices(
proxyMap map[string]v1.ProxyConfigurer,
visitorMap map[string]v1.VisitorConfigurer,
) ([]v1.ProxyConfigurer, []v1.VisitorConfigurer) {
proxies := slices.SortedFunc(maps.Values(proxyMap), func(x, y v1.ProxyConfigurer) int {
return cmp.Compare(x.GetBaseConfig().Name, y.GetBaseConfig().Name)View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Inspect the wrapped error chain (errors.Unwrap / string suffix) to find which source and why
- Fix that source's underlying file/content
- For watched sources, write changes atomically (write temp file + rename) so the watcher never reads a half-written config
Example fix
# before: echo 'bad toml' >> /etc/frp/frpc.toml while running # after: write /etc/frp/frpc.toml.tmp fully, then mv /etc/frp/frpc.toml.tmp /etc/frp/frpc.toml
Defensive patterns
Strategy: try-catch
Validate before calling
// probe each source before wiring the aggregator
for _, src := range sources { if _, _, err := src.Load(); err != nil { return fmt.Errorf("source %T unhealthy: %w", src, err) } } Try / catch
if _, _, err := aggregator.Load(); err != nil {
var cause error = errors.Unwrap(err) // 'load source: %w' keeps chain
for cause != nil && errors.Unwrap(cause) != nil { cause = errors.Unwrap(cause) }
// log full chain; identify failing source type
} Prevention
- Use %w-friendly inspection (errors.As/Is) rather than string matching
- Write watched config files atomically (tmp+rename)
- Keep the store file backed up and JSON-validated
When it happens
Trigger: Any configured source failing to load: a file source watching a config that becomes invalid, or a store source whose JSON file is corrupted. Sources load in registration order, so the first failure wins.
Common situations: frpc deployments with multiple config sources (static file + dynamic store); editing the live-watched config file into a temporarily invalid state; a corrupted store JSON after disk issues.
Related errors
- parse config error: %v
- no sources configured
- ErrAlreadyExists
- plugin type is empty
- visitor plugin type is empty
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/24976bd50a795172.
Report an issue: GitHub.