grafana/k6 · error
99
99
Error message
thresholds on metrics '%s' have been crossed
What it means
After the test ends and all samples are flushed to outputs, k6 evaluates thresholds one final time. If any were crossed, the run is failed with the list of breached metrics, exit code 99 (ThresholdsHaveFailed), and abort reason AbortedByThresholdsAfterTestEnd. This is k6's SLO gate: execution completed normally, but the acceptance criteria in options.thresholds were not met.
Solutions
- Check which metrics crossed — the message names them and the end-of-test summary marks those threshold rows ✗
- Fix the underlying performance problem in the system under test (the usual intent of the gate)
- If a threshold is wrong or unrealistic, adjust it in export const options = { thresholds: {...} }
- In CI, map exit code 99 to a distinct 'performance gate failed' status and archive the summary report
Example fix
// before
export const options = {
thresholds: { http_req_duration: ['p(95)<100'] },
};
// after (if 100ms was an unrealistic target for this environment)
export const options = {
thresholds: { http_req_duration: ['p(95)<300'] },
}; Defensive patterns
Strategy: try-catch
Try / catch
# treat 99 as the performance-gate result, not a generic error k6 run script.js ec=$? case $ec in 0) echo "passed";; 99) echo "thresholds crossed — performance gate failed"; exit 99;; *) echo "k6 failed with exit code $ec"; exit $ec;; esac
Prevention
- Baseline new thresholds with a smoke run before enforcing them in CI gates
- Set thresholds from realistic measured values (p95 plus headroom), not aspirational numbers
- Keep threshold definitions in one reviewed place in the script so failures are traceable
- Know the exit codes: 99 thresholds, 105 aborted, 110 script-marked-failed, 104 invalid config
When it happens
Trigger: Declaring thresholds like `export const options = { thresholds: { http_req_duration: ['p(95)<300'] } }` (or http_req_failed, checks, custom metrics) whose measured values exceed the limit at end of test; also applies to k6 cloud runs.
Common situations: CI pipelines using k6 as a pass/fail performance gate; regression detection after a deploy; thresholds copied from another environment with very different latency baselines.
Related errors
AI-assisted analysis of grafana/k6@3fcf5388d7 (2026-08-18).
Data as JSON: /api/errors/714425729d8824b3.
Report an issue: GitHub.
Appendix: source
Thrown at internal/cmd/run.go:376
}()
if thresholdsEnabled {
finalizeThresholds := metricsEngine.StartThresholdCalculations(
metricsIngester, runAbort, executionState.GetCurrentTestRunDuration,
)
handleFinalThresholdCalculation := func() {
// This gets called after the Samples channel has been closed and
// the OutputManager has flushed all of the cached samples to
// outputs (including MetricsEngine's ingester). So we are sure
// there won't be any more metrics being sent.
logger.Debug("Finalizing thresholds...")
breachedThresholds := finalizeThresholds()
if len(breachedThresholds) == 0 {
return
}
tErr := errext.WithAbortReasonIfNone(
errext.WithExitCodeIfNone(
fmt.Errorf("thresholds on metrics '%s' have been crossed", strings.Join(breachedThresholds, ", ")),
exitcodes.ThresholdsHaveFailed,
), errext.AbortedByThresholdsAfterTestEnd)
if err == nil {
err = tErr
} else {
logger.WithError(tErr).Debug("Crossed thresholds, but test already exited with another error")
}
}
if finalizeThresholds != nil {
defer handleFinalThresholdCalculation()
}
}
defer func() {
logger.Debug("Waiting for metrics processing to finish...")
close(samples)
waitOutputsFlushed()View on GitHub (pinned to 3fcf5388d7)