{"record":{"id":"714425729d8824b3","repo":"grafana/k6","slug":"99-714425","errorCode":"99","errorMessage":"thresholds on metrics '%s' have been crossed","messagePattern":"thresholds on metrics '(.+?)' have been crossed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cmd/run.go","lineNumber":364,"sourceCode":"\t}()\n\n\tif thresholdsEnabled {\n\t\tfinalizeThresholds := metricsEngine.StartThresholdCalculations(\n\t\t\tmetricsIngester, runAbort, executionState.GetCurrentTestRunDuration,\n\t\t)\n\t\thandleFinalThresholdCalculation := func() {\n\t\t\t// This gets called after the Samples channel has been closed and\n\t\t\t// the OutputManager has flushed all of the cached samples to\n\t\t\t// outputs (including MetricsEngine's ingester). So we are sure\n\t\t\t// there won't be any more metrics being sent.\n\t\t\tlogger.Debug(\"Finalizing thresholds...\")\n\t\t\tbreachedThresholds := finalizeThresholds()\n\t\t\tif len(breachedThresholds) == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttErr := errext.WithAbortReasonIfNone(\n\t\t\t\terrext.WithExitCodeIfNone(\n\t\t\t\t\tfmt.Errorf(\"thresholds on metrics '%s' have been crossed\", strings.Join(breachedThresholds, \", \")),\n\t\t\t\t\texitcodes.ThresholdsHaveFailed,\n\t\t\t\t), errext.AbortedByThresholdsAfterTestEnd)\n\n\t\t\tif err == nil {\n\t\t\t\terr = tErr\n\t\t\t} else {\n\t\t\t\tlogger.WithError(tErr).Debug(\"Crossed thresholds, but test already exited with another error\")\n\t\t\t}\n\t\t}\n\t\tif finalizeThresholds != nil {\n\t\t\tdefer handleFinalThresholdCalculation()\n\t\t}\n\t}\n\n\tdefer func() {\n\t\tlogger.Debug(\"Waiting for metrics and traces processing to finish...\")\n\t\tclose(samples)\n","sourceCodeStart":346,"sourceCodeEnd":382,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/cmd/run.go#L346-L382","documentation":"After a test finishes and all samples are flushed to outputs, k6 performs a final threshold evaluation via handleFinalThresholdCalculation. If any threshold expression was crossed, k6 returns this error listing the breached metric names and exits with code 99 (ThresholdsHaveFailed), with abort reason AbortedByThresholdsAfterTestEnd. This is k6's intended mechanism for making thresholds load-bearing in CI.","triggerScenarios":"Running 'k6 run script.js' where options.thresholds contain conditions that evaluate true at end of test, e.g. http_req_duration: ['p(95)<500'] when measured p95 was above 500ms. The check runs in a deferred handler after the Samples channel has closed and the MetricsEngine ingester has drained.","commonSituations":"The system under test is slower than thresholds assume; thresholds are too strict for the configured load level; aborted runs re-evaluate thresholds on partial data; CI treats exit 99 as a build failure (usually the desired behavior).","solutions":["Read the metric names in the message and compare actual values against thresholds in the end-of-test summary","Fix the performance problem or reduce the load (fewer VUs, lower arrival rate) so thresholds pass","Relax the threshold expressions in options.thresholds to realistic targets","For exploratory runs only, skip threshold evaluation with --no-thresholds or K6_NO_THRESHOLDS=true (not recommended for CI)"],"exampleFix":"// before\nexport const options = { thresholds: { http_req_duration: ['p(95)<500'] } };\n// after (calibrated to measured performance)\nexport const options = { thresholds: { http_req_duration: ['p(95)<800'] } };","handlingStrategy":"validation","validationCode":"# Review the effective thresholds before running\nk6 inspect script.js | jq '.thresholds'\n# Branch on the exit code so 99 (SLO breach) is distinct from other failures\nk6 run script.js; rc=$?\nif [ $rc -eq 99 ]; then echo 'quality gate failed: thresholds crossed'; exit 99; fi\nexit $rc","typeGuard":null,"tryCatchPattern":"In CI, branch on exit code: 0 = pass, 99 = thresholds crossed (quality gate), 104 = misconfiguration, 105 = canceled. Don't collapse them into one 'failed' bucket, and attach the end-of-test summary as build evidence.","preventionTips":["Baseline the system and calibrate thresholds from measured p95/p99 before enforcing them","Prefer rate thresholds with tolerance (e.g. 'rate<0.01') over absolute latency in noisy environments","Use abortOnFail deliberately — it stops the run early instead of just failing the exit code","Keep thresholds in version control and review them like code"],"tags":["thresholds","metrics","testing","exit-code-99"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}