{"record":{"id":"6931b5399af4599f","repo":"grafana/k6","slug":"invalid-threshold","errorCode":null,"errorMessage":"invalid threshold","messagePattern":"invalid threshold","errorType":"validation","errorClass":"ErrInvalidThreshold","httpStatus":null,"severity":"error","filePath":"metrics/thresholds.go","lineNumber":236,"sourceCode":"}\n\n// Parse parses the Thresholds and fills each Threshold.parsed field with the result.\n// It effectively asserts they are syntaxically correct.\nfunc (ts *Thresholds) Parse() error {\n\tfor _, t := range ts.Thresholds {\n\t\tparsed, err := parseThresholdExpression(t.Source)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tt.parsed = parsed\n\t}\n\n\treturn nil\n}\n\n// ErrInvalidThreshold indicates a threshold is not valid\nvar ErrInvalidThreshold = errors.New(\"invalid threshold\")\n\n// Validate ensures a threshold definition is consistent with the metric it applies to.\n// Given a metric registry and a metric name to apply the expressions too, Validate will\n// assert that each threshold expression uses an aggregation method that's supported by the\n// provided metric. It returns an error otherwise.\n// Note that this function expects the passed in thresholds to have been parsed already, and\n// have their Parsed (ThresholdExpression) field already filled.\nfunc (ts *Thresholds) Validate(metricName string, r *Registry) error {\n\tparsedMetricName, _, err := ParseMetricName(metricName)\n\tif err != nil {\n\t\tparseErr := fmt.Errorf(\"unable to validate threshold expressions; reason: %w\", err)\n\t\treturn errext.WithExitCodeIfNone(parseErr, exitcodes.InvalidConfig)\n\t}\n\n\t// Obtain the metric the thresholds apply to from the registry.\n\t// if the metric doesn't exist, then we return an error indicating\n\t// the InvalidConfig exitcode should be used.\n\tmetric := r.Get(parsedMetricName)","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/metrics/thresholds.go#L218-L254","documentation":"ErrInvalidThreshold (metrics/thresholds.go:236) is the sentinel for Thresholds.Validate failures. It fires in two cases: the metric named in the threshold config does not exist in the registry (line 256: 'no metric name %q found'), or a threshold expression uses an aggregation method unsupported by the metric's type (line 276-285, checked via MetricType.supportsAggregationMethod). Both are wrapped with errext.WithExitCodeIfNone(..., exitcodes.InvalidConfig), so k6 exits with the InvalidConfig code.","triggerScenarios":"thresholds: { 'nonexistent_metric': ['value<10'] }, or a type/method mismatch such as 'http_req_duration': ['rate<100'] (rate is only for counters/rates) or 'checks': ['p(99)<0.9'] (percentiles only for trends). Supported methods: counter -> count,rate; gauge -> value; rate -> rate; trend -> avg,min,max,med,p(NN).","commonSituations":"Copy-pasting thresholds between metric types; thresholds on custom metrics before they are created (a custom metric referenced only in thresholds is not in the registry); renaming a metric but not its threshold; using value on a counter.","solutions":["If the metric name is misspelled or missing, create it with counter/gauge/trend/rate from 'k6/metrics' or correct the name to a built-in metric (http_req_duration, checks, data_received, ...)","If the method is unsupported, match it to the type: trend -> avg/min/max/med/p(95); counter -> count/rate; gauge -> value; rate -> rate","Check errors.Is(err, metrics.ErrInvalidThreshold) and the exit code InvalidConfig when handling programmatically"],"exampleFix":"// before\nexport const options = {\n  thresholds: { http_req_duration: ['rate<100'] },\n};\n\n// after\nexport const options = {\n  thresholds: { http_req_duration: ['p(95)<800'] },\n};","handlingStrategy":"validation","validationCode":"// JS: sanity-check threshold keys/methods before a run\nconst METHODS = {\n  counter: ['count', 'rate'], gauge: ['value'], rate: ['rate'],\n  trend: ['avg', 'min', 'max', 'med', 'p(90)', 'p(95)', 'p(99)'],\n};\nfor (const [metric, ths] of Object.entries(options.thresholds || {})) {\n  const type = metricType(metric); // from your own registry of custom + built-in metrics\n  for (const th of ths) {\n    const m = th.match(/^(count|rate|value|avg|min|max|med|p\\(\\d+\\))/);\n    if (type && (!m || !METHODS[type].includes(m[1]))) {\n      throw new Error(`threshold ${th} not valid for ${metric} (${type})`);\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"// Go\nif err := th.Validate(name, reg); err != nil {\n    if errors.Is(err, metrics.ErrInvalidThreshold) {\n        // err already carries exitcodes.InvalidConfig; report and fix config, do not retry\n    }\n}","preventionTips":["Match aggregation method to metric type: trend->avg/min/max/med/p(NN), counter->count/rate, gauge->value, rate->rate","Threshold targets must exist in the registry: create custom metrics with newCounter/newTrend/... before referencing them","Note that thresholds on sub-metrics like 'http_req_duration{status:200}' inherit the parent's type (trend)"],"tags":["metrics","thresholds","validation","config"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}