grafana/k6 · error
110
110
Error message
test run was marked as failed
What it means
execution.test.fail(msg) (k6/execution, also surfaced via the experimental test module) marks the whole test as failed while letting the run finish — unlike test.abort(), which interrupts it. After execution completes, run() checks TestStatus.Failed() and returns this error with exit code 110 (MarkedAsFailed). The reason string passed to fail() is also logged at ERROR level during the run, which is the fastest way to find the call site.
Solutions
- Search the script for 'test.fail' and read the matching ERROR log line — it contains the reason string you passed
- Fix the condition that triggered the failure (usually a real application error the script correctly detected)
- If the condition should not be fatal, downgrade to check() or console.error instead of test.fail()
- In CI, map exit code 110 to 'script-asserted failure', distinct from 99 (thresholds) and 105 (aborted)
Example fix
// before
import execution from 'k6/execution';
if (res.status >= 500) execution.test.fail('got 5xx');
// after (non-fatal assertion)
import { check } from 'k6';
check(res, { 'status is 2xx': (r) => r.status >= 200 && r.status < 300 }); Defensive patterns
Strategy: validation
Try / catch
# map exit 110 to a script-assertion failure bucket k6 run script.js ec=$? case $ec in 110) echo "script called execution.test.fail() — see ERROR lines in the log for the reason"; exit 110;; *) exit $ec;; esac
Prevention
- Use execution.test.fail() only for conditions that must fail the run; use check() for non-fatal assertions
- Always pass a descriptive reason string — it appears in the ERROR log and is your only call-site breadcrumb
- Search scripts for 'test.fail' when exit code 110 appears; the log line 'test was marked as failed: <reason>' pinpoints it
- Keep the k6/execution import at the top so grep finds all fail/abort call sites quickly
When it happens
Trigger: Calling execution.test.fail('login endpoint returned 5xx') anywhere in VU code when an application-level condition is detected; the run then completes all iterations and exits 110 instead of 0.
Common situations: Business-assertion scripts that must fail the run on content errors (wrong JSON, missing field) that HTTP metrics and thresholds don't capture; smoke tests asserting critical flows.
Related errors
AI-assisted analysis of grafana/k6@94169daab2 (2026-08-18).
Data as JSON: /api/errors/6464a16f36f79e82.
Report an issue: GitHub.
Appendix: source
Thrown at internal/cmd/run.go:487
// Check what the execScheduler.Run() error is.
if err != nil {
err = common.UnwrapSobekInterruptedError(err)
logger.WithError(err).Debug("Test finished with an error")
return err
}
// Warn if no iterations could be completed.
if executionState.GetFullIterationCount() == 0 {
logger.Warn("No script iterations fully finished, consider making the test duration longer")
}
// The execution module enables users to mark a test as failed, while letting the test
// execution complete. As such, we check the test status here, after the test run has finished, and
// ensure we return an error indicating that the test run was marked as failed, and the proper
// exit code is used.
if testRunState.TestStatus.Failed() {
return errext.WithExitCodeIfNone(
fmt.Errorf("test run was marked as failed"),
exitcodes.MarkedAsFailed,
)
}
logger.Debug("Test finished cleanly")
return nil
}
func getSummaryMode(runtimeOptions lib.RuntimeOptions) (summary.Mode, bool, error) {
sm, err := summary.ValidateMode(runtimeOptions.SummaryMode.String)
if err != nil {
return summary.ModeDisabled, false, err
}
return sm, sm != summary.ModeDisabled, nil
}
View on GitHub (pinned to 94169daab2)