k6 "thresholds have been crossed" (exit code 99) in CI
By Daniel Zoghalchali·Latchkey
k6 exits with code 99 when one or more thresholds you defined were crossed during the run. This is a pass/fail gate working as designed: the load test ran, but a metric (p95 latency, error rate) missed its target.
What this error means
k6 prints a summary with a red cross next to a threshold line and the step fails with "some thresholds have been crossed" and exit code 99, distinct from an execution error.
k6
✗ http_req_duration..............: avg=812ms p(95)=1.94s
{ expected: p(95)<500 }
ERRO[0031] some thresholds have been crossed
exit status 99
Diagnose it: is the test deterministic?
Before debugging an assertion, establish whether the test fails consistently. A test that passes alone and fails in the suite is sharing state; one that fails intermittently is racing something. Neither is fixed in the assertion.
Terminal
# in isolation
<runner> path/to/one.test
# order dependence
<runner> --shuffle # or the runner equivalent
# raciness
for i in $(seq 1 20); do <runner> path/to/one.test || break; done
Common causes
A real metric missed its target
The service under test was slower or errored more than your thresholds allow, so k6 fails the run to block a regression from merging.
The threshold is stricter than the environment can meet
A CI runner or ephemeral target is slower than production, so a p(95)<500 gate calibrated for prod fails on undersized infrastructure.
How to fix it
Read which threshold failed and fix the regression
Find the threshold line marked with a cross in the summary.
Confirm whether the app regressed or the target was starved of resources.
Fix the code path, or right-size the target service before re-running.
Keep the gate honest by setting thresholds against the environment CI actually tests, not against production hardware.
script.js
thresholds: {
http_req_duration: ['p(95)<1500'], // CI target is smaller than prod
},
How to prevent it
Define thresholds so exit code 99 is the intended release gate.
Match threshold targets to the size of the CI environment under test.
Track the summary over time so a real regression is obvious.
Frequently asked questions
What causes k6 "thresholds have been crossed" (exit code 99) in CI?
There are 2 common causes: a real metric missed its target and the threshold is stricter than the environment can meet. The service under test was slower or errored more than your thresholds allow, so k6 fails the run to block a regression from merging.
How do I fix k6 "thresholds have been crossed" (exit code 99) in CI?
There are 2 fixes depending on which cause you have: read which threshold failed and fix the regression and calibrate thresholds to the ci target. Work through them in order, since the first is the most common.
What does k6 "thresholds have been crossed" (exit code 99) in CI actually mean?
k6 prints a summary with a red cross next to a threshold line and the step fails with "some thresholds have been crossed" and exit code 99, distinct from an execution error.
How do I stop k6 "thresholds have been crossed" (exit code 99) in CI happening again?
Define thresholds so exit code 99 is the intended release gate. The prevention section lists 3 changes that keep it from recurring.