grafana/k6 · error
error for stage %d: %w
Error message
error for stage %d: %w
What it means
Emitted while parsing the repeatable --stage flag (e.g. `k6 run --stage 1m:10,...`). Each stage string is split on ':' and decoded by lib.Stage.UnmarshalText: the left side must be a Go duration (time.ParseDuration) and the right side a plain integer VU target. Any duration without a unit, non-numeric target, or extra malformed text fails with this per-index wrap, where %d is the position (0-based) in the --stage list.
Source
Thrown at internal/cmd/options.go:114
NoConnectionReuse: getNullBool(flags, "no-connection-reuse"),
NoVUConnectionReuse: getNullBool(flags, "no-vu-connection-reuse"),
MinIterationDuration: getNullDuration(flags, "min-iteration-duration"),
Throw: getNullBool(flags, "throw"),
DiscardResponseBodies: getNullBool(flags, "discard-response-bodies"),
MetricSamplesBufferSize: null.NewInt(1000, false),
}
// Using Changed() because GetStringSlice() doesn't differentiate between empty and no value
if flags.Changed("stage") {
stageStrings, err := flags.GetStringSlice("stage")
if err != nil {
return opts, err
}
opts.Stages = []lib.Stage{}
for i, s := range stageStrings {
var stage lib.Stage
if err := stage.UnmarshalText([]byte(s)); err != nil {
return opts, fmt.Errorf("error for stage %d: %w", i, err)
}
if !stage.Duration.Valid {
return opts, fmt.Errorf("stage %d doesn't have a specified duration", i)
}
opts.Stages = append(opts.Stages, stage)
}
}
if flags.Changed("execution-segment") {
executionSegmentStr, err := flags.GetString("execution-segment")
if err != nil {
return opts, err
}
segment := new(lib.ExecutionSegment)
err = segment.UnmarshalText([]byte(executionSegmentStr))
if err != nil {
return opts, err
}View on GitHub (pinned to 93accf6570)
Solutions
- Use Go duration syntax on the left of ':' — always with a unit: `--stage 30s:10 --stage 1m:5 --stage 10s:0`
- Keep the target side a bare integer (VUs), no quotes/decimals
- Count from 0: the index in the message points at the exact --stage occurrence to fix
- Prefer a JSON/YAML options block (stages: [...]) when the ramp gets complex, instead of long flag chains
Example fix
# before k6 run --stage 20:10 --stage 1m:5 script.js # error: error for stage 0: time: missing unit in duration "20" # after k6 run --stage 20s:10 --stage 1m:5 script.js
Defensive patterns
Strategy: validation
Validate before calling
# Validate --stage strings: '<duration-with-unit>:<integer>' before running
for s in "${stages[@]}"; do
[[ "$s" =~ ^[0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h):[0-9]+$ ]] \
|| { echo "bad stage '$s' (want e.g. 30s:10)"; exit 1; }
done
k6 run ${stages[@]/#/--stage } script.js Type guard
function isValidStageFlag(s) {
return /^[0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h):[0-9]+$/.test(s.trim());
} Prevention
- Always write durations with explicit units (30s, 1m) — k6 never assumes seconds
- Keep targets as plain integers; no decimals, no quotes
- For more than ~3 stages, move them into the script's options.stages instead of CLI flags
When it happens
Trigger: `--stage 10:5` (duration '10' has no unit, needs '10s'), `--stage 1m:five`, `--stage 1m:10:extra`, or a stage copied from JSON config syntax (`{"duration":"1m","target":10}`) pasted verbatim into the flag.
Common situations: Assuming seconds-by-default like some other tools; converting ramping-vus JSON options into CLI flags by hand; shell loops concatenating stage strings with stray separators.
Related errors
- stage %d doesn't have a specified duration
- tests with unspecified duration are not allowed when outputt
- failed to parse template file %s: %w
- Run `k6 cloud login` to authenticate, or check the docs for
- access token not configured
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/91f7d82ad116ed9b.
Report an issue: GitHub.