Fail CI Builds with k6 Thresholds and abortOnFail
Fail CI pipelines automatically when k6 load tests breach latency or error‑rate SLOs by defining thresholds, using abortOnFail, and tagging requests. This guide shows how to set up, run, and verify thresholds in CI.
11 Aug 2025, 17:45 UTC

Desired Outcome
Automatically fail a CI pipeline when a load test exceeds a latency or error‑rate Service Level Objective (SLO). The test should stop early if the failure is obvious, saving compute time and providing a clear exit code for the CI system.
Prerequisites
- k6 ≥ 0.34.0 (threshold syntax and abortOnFail are stable from 0.31.0). Run
k6 versionto confirm. - CI environment that interprets a non‑zero exit code as a failure (GitHub Actions, GitLab CI, Jenkins, etc.).
- Basic familiarity with JavaScript and k6’s
optionsobject.
Step 1 – Define Global Thresholds
Thresholds live inside options.thresholds. Each key is a metric name; the value can be a string expression or an array of expressions. Example: fail if the 95th percentile latency is above 500 ms.
export const options = {
thresholds: {
http_req_duration: ['p(95)<500'], // 95th percentile < 500 ms
http_req_failed: ['rate<0.01'] // error rate < 1 %
}
};
When any threshold fails, k6 exits with code 1, which a CI system will interpret as a build failure.
Step 2 – Enable abortOnFail and delayAbortEval
To stop the test immediately after a threshold breach, add abortOnFail to the threshold definition. delayAbortEval gives a grace period so that a short warm‑up spike doesn’t abort the run.
export const options = {
thresholds: {
http_req_duration: {
threshold: 'p(95)<500',
abortOnFail: true,
delayAbortEval: '30s' // wait 30 s after start before evaluating
}
}
};
Run the test with k6 run script.js. If the 95th percentile exceeds 500 ms after the first 30 s, k6 prints a failure message and exits immediately.
Step 3 – Tag‑Specific Thresholds
When a script hits multiple endpoints, you can enforce different SLOs per endpoint by tagging requests and filtering thresholds. Add a tags object to the request:
http.batch([{
method: 'GET',
url: '/checkout',
tags: { endpoint: 'checkout' }
}]);
Then declare a threshold that applies only to the checkout endpoint:
export const options = {
thresholds: {
'http_req_duration{endpoint:checkout}': ['p(99)<800']
}
};
Only requests tagged with endpoint:checkout are evaluated against this rule, allowing faster endpoints to mask slow ones if you don’t tag.
Step 4 – Custom Metric Thresholds
Business logic often requires metrics beyond the built‑ins. Create a Rate metric that tracks successful checkouts and enforce a high success ratio:
import { Rate } from 'k6/metrics';
export const checkoutSuccess = new Rate('checkout_success');
export default function () {
const res = http.get('/checkout');
if (res.status === 200) {
checkoutSuccess.add(1);
} else {
checkoutSuccess.add(0);
}
}
export const options = {
thresholds: {
checkout_success: ['rate>0.99']
}
};
Now the pipeline fails if the success rate drops below 99 %.
Step 5 – Run, Verify, and Interpret the Report
- Execute
k6 run script.js. - On a threshold breach, k6 prints a line like
THRESHOLD FAILED: http_req_duration p(95)<500and exits with code 1. - The end‑of‑test summary shows a table with each threshold and a checkmark or cross.
- In CI, a non‑zero exit code will mark the job as failed; no output parsing is required.
Expected Checks
- Confirm the exit code:
echo $?should return1on failure. - Inspect the summary table for the
✓or✗symbols. - Verify that
delayAbortEvalprevented premature abort by checking theAbort evaluation delayedline in the log.
Recovery Options
- If a test aborts too early, increase
delayAbortEvalor add a ramp‑up stage:stages: [{duration: '5m', target: 100}]. - For flaky error‑rate thresholds, adjust the expression to
rate<0.02or use a moving‑average metric. - When thresholds mask slow endpoints, tag all requests and add per‑tag thresholds.
Limitations and Practical Checks
- Threshold syntax changes in newer k6 releases; always consult the official docs matching your version.
- abortOnFail can be overly aggressive if the load profile has a long warm‑up; use
delayAbortEvalor a dedicated warm‑up stage. - Custom metrics must be added to
options.thresholdswith the same name as the exported metric. - CI systems that ignore exit codes will not gate the pipeline; ensure the runner checks
$?.
Concrete Example
Below is a minimal script that fails a GitHub Actions job when checkout latency exceeds 800 ms or error rate exceeds 2 %:
import http from 'k6/http';
import { Rate } from 'k6/metrics';
export const checkoutSuccess = new Rate('checkout_success');
export const options = {
vus: 50,
duration: '1m',
thresholds: {
'http_req_duration{endpoint:checkout}': ['p(99)<800', {
abortOnFail: true,
delayAbortEval: '30s'
}],
'http_req_failed{endpoint:checkout}': ['rate<0.02'],
checkout_success: ['rate>0.98']
}
};
export default function () {
const res = http.get('/checkout', { tags: { endpoint: 'checkout' } });
if (res.status === 200) {
checkoutSuccess.add(1);
} else {
checkoutSuccess.add(0);
}
}
Run k6 run checkout.js locally or in CI. If any threshold fails, the job exits with code 1 and the CI pipeline marks the build as failed.
Conclusion
By declaring precise thresholds, leveraging abortOnFail and delayAbortEval, and tagging requests, you can make k6 an effective gatekeeper for your CI/CD pipeline. The exit‑code mechanism eliminates the need for output parsing, and custom metrics let you enforce business‑specific SLOs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.