Skip to content
Latchkey

k6 "thresholds have been crossed" (exit code 99) in CI

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

  1. Find the threshold line marked with a cross in the summary.
  2. Confirm whether the app regressed or the target was starved of resources.
  3. Fix the code path, or right-size the target service before re-running.
script.js
export const options = {
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.01'],
  },
};

Calibrate thresholds to the CI target

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.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card