{"record":{"id":"0491b244bfb11b6e","repo":"slackhq/nebula","slug":"stats-listen-should-not-be-empty","errorCode":null,"errorMessage":"stats.listen should not be empty","messagePattern":"stats\\.listen should not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"stats.go","lineNumber":357,"sourceCode":"\t}\n\n\tswitch cfg.typ {\n\tcase \"graphite\":\n\t\tcfg.graphite.protocol = c.GetString(\"stats.protocol\", \"tcp\")\n\t\tcfg.graphite.host = c.GetString(\"stats.host\", \"\")\n\t\tif cfg.graphite.host == \"\" {\n\t\t\treturn cfg, errors.New(\"stats.host can not be empty\")\n\t\t}\n\t\taddr, err := net.ResolveTCPAddr(cfg.graphite.protocol, cfg.graphite.host)\n\t\tif err != nil {\n\t\t\treturn cfg, fmt.Errorf(\"error while setting up graphite sink: %s\", err)\n\t\t}\n\t\tcfg.graphite.resolvedAddr = addr.String()\n\t\tcfg.graphite.prefix = c.GetString(\"stats.prefix\", \"nebula\")\n\tcase \"prometheus\":\n\t\tcfg.prom.listen = c.GetString(\"stats.listen\", \"\")\n\t\tif cfg.prom.listen == \"\" {\n\t\t\treturn cfg, errors.New(\"stats.listen should not be empty\")\n\t\t}\n\t\tcfg.prom.path = c.GetString(\"stats.path\", \"\")\n\t\tif cfg.prom.path == \"\" {\n\t\t\treturn cfg, errors.New(\"stats.path should not be empty\")\n\t\t}\n\t\tcfg.prom.namespace = c.GetString(\"stats.namespace\", \"\")\n\t\tcfg.prom.subsystem = c.GetString(\"stats.subsystem\", \"\")\n\tdefault:\n\t\treturn cfg, fmt.Errorf(\"stats.type was not understood: %s\", cfg.typ)\n\t}\n\n\treturn cfg, nil\n}\n","sourceCodeStart":339,"sourceCodeEnd":371,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/stats.go#L339-L371","documentation":"When stats.type is \"prometheus\", nebula requires stats.listen to specify the local address:port the Prometheus HTTP endpoint binds to. An empty value is rejected at config load/reload time with this error, preventing nebula from starting with an unbindable stats endpoint.","triggerScenarios":"Setting `stats.type: prometheus` without a `stats.listen` value during startup or SIGHUP reload; loadStatsConfig reads c.GetString(\"stats.listen\", \"\") and gets an empty string.","commonSituations":"Switching from graphite to prometheus configs but keeping only stats.host; forgetting stats.listen entirely; YAML key typo (e.g. stat.listen) so the value silently falls back to \"\".","solutions":["Add `stats.listen: 127.0.0.1:8080` (or similar bind address) under the stats block","Check for YAML typos/indentation so stats.listen actually lands inside the stats block","Also set stats.path (required next) so the prometheus sink validates fully"],"exampleFix":"# before\nstats:\n  type: prometheus\n  path: /metrics\n# after\nstats:\n  type: prometheus\n  listen: 127.0.0.1:8080\n  path: /metrics","handlingStrategy":"validation","validationCode":"if cfg.Stats[\"type\"] == \"prometheus\" && (cfg.Stats[\"listen\"] == nil || cfg.Stats[\"listen\"] == \"\") {\n    return errors.New(\"stats.listen must be set when stats.type is prometheus\")\n}","typeGuard":null,"tryCatchPattern":"cfg, err := loadStatsConfig(raw)\nif err != nil {\n    if strings.Contains(err.Error(), \"stats.listen should not be empty\") {\n        return fmt.Errorf(\"prometheus stats needs stats.listen, e.g. 127.0.0.1:8080: %w\", err)\n    }\n    return err\n}","preventionTips":["Always set stats.listen when using stats.type: prometheus","Bind to 127.0.0.1 unless exposing metrics deliberately","Validate config before reload to avoid dropping a running tunnel","Watch for key typos that silently fall back to the empty default"],"tags":["nebula","config","prometheus","stats","validation"],"backgroundTag":"missing-config-key","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}