{"record":{"id":"abbae5d2e88fe745","repo":"slackhq/nebula","slug":"stats-interval-was-an-invalid-duration-s","errorCode":null,"errorMessage":"stats.interval was an invalid duration: %s","messagePattern":"stats\\.interval was an invalid duration: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"stats.go","lineNumber":338,"sourceCode":"\t\tcase <-t.C:\n\t\t\tfor _, fn := range fns {\n\t\t\t\tfn()\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc loadStatsConfig(c *config.C) (statsConfig, error) {\n\tcfg := statsConfig{\n\t\ttyp: c.GetString(\"stats.type\", \"\"),\n\t}\n\tif cfg.typ == \"\" || cfg.typ == \"none\" {\n\t\treturn cfg, nil\n\t}\n\n\tcfg.interval = c.GetDuration(\"stats.interval\", 0)\n\tif cfg.interval <= 0 {\n\t\treturn cfg, fmt.Errorf(\"stats.interval was an invalid duration: %s\", c.GetString(\"stats.interval\", \"\"))\n\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 == \"\" {","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/stats.go#L320-L356","documentation":"loadStatsConfig reads stats.interval as a duration when a stats message type other than 'none' is enabled. If the value is missing, non-numeric, or <= 0 after parsing, the config load fails with this message echoing the raw string. Raised during reload/startup before any stats sink is created.","triggerScenarios":"stats.interval is absent, set to a non-duration string ('5', 'every minute', '1m0s typo'), a bare integer without a unit, or an explicit non-positive value while stats.type is graphite or prometheus.","commonSituations":"Writing interval: 10 instead of 10s in YAML; commenting out interval while keeping stats.type enabled; locale/typo issues like '30sec' instead of '30s'.","solutions":["Set stats.interval to a valid Go duration with units, e.g. 10s, 1m","If you don't want metrics, set stats.type: none instead of removing interval","Ensure the YAML key is under the stats: block and correctly indented","Use unitless values with Go duration parsing rules only if intended — safest to always include s/m/h"],"exampleFix":"// before\nstats:\n  type: graphite\n  interval: 10\n// after\nstats:\n  type: graphite\n  interval: 10s","handlingStrategy":"validation","validationCode":"# Go: validate the duration the same way nebula does before deploying\nif d, err := time.ParseDuration(cfg.Stats.Interval); err != nil || d <= 0 {\n\tlog.Fatalf(\"stats.interval must be a positive Go duration like 10s, got %q\", cfg.Stats.Interval)\n}","typeGuard":null,"tryCatchPattern":"try {\n  loadConfig(path)\n} catch (e) {\n  if (e.message.includes(\"stats.interval was an invalid duration\")) {\n    console.error(\"set stats.interval with units, e.g. 10s; or use stats.type: none\")\n  }\n  throw e\n}","preventionTips":["Always write durations with units (10s, 1m) — bare integers fail","If metrics are disabled, set stats.type: none rather than deleting interval","Lint nebula configs in CI for required stats keys when any sink type is enabled"],"tags":["config","stats","duration","validation","nebula"],"backgroundTag":"invalid-duration-config","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"}