grafana/k6 · error
stage %d doesn't have a specified duration
Error message
stage %d doesn't have a specified duration
What it means
Companion check in the same --stage loop: the stage string parsed successfully but produced no duration (empty left side of ':'), which happens with a target-only stage like `--stage :10`. lib.Stage.UnmarshalText happily decodes target-only stages (duration is optional at the text level), but the CLI requires every --stage to carry an explicit duration, so the %d-indexed stage is rejected here.
Source
Thrown at internal/cmd/options.go:117
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
}
opts.ExecutionSegment = segment
}
View on GitHub (pinned to 93accf6570)
Solutions
- Always prefix a duration: `--stage 1s:100` for a near-instant jump to 100 VUs
- Check the flagged index (0-based) among your --stage occurrences and add the missing 'Nm:' prefix
- For plateau-then-drop patterns, spell each stage explicitly: `--stage 1m:100 --stage 1m:100`
- Move complex ramps into the script's options.stages where each entry requires duration by schema and errors are clearer
Example fix
# before k6 run --stage :100 script.js # error: stage 0 doesn't have a specified duration # after k6 run --stage 1s:100 script.js
Defensive patterns
Strategy: validation
Validate before calling
# Require a non-empty duration on the left of ':' in every --stage
for s in "${stages[@]}"; do
[[ "$s" =~ ^.+:[0-9]*$ && ! "$s" =~ ^:[0-9]*$ ]] || { echo "stage '$s' needs a duration (e.g. 1s:100)"; exit 1; }
done Type guard
function stageHasDuration(s) {
const left = s.split(":")[0];
return left.trim() !== "" && /^[0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h)$/.test(left.trim());
} Prevention
- Express instant VU jumps as a short duration: 1s:100, never :100
- Build stage strings from {duration,target} pairs in code so the duration field can't be dropped
- Validate the full stage list in one place before passing it to k6
When it happens
Trigger: `k6 run --stage :100 script.js` (intended 'ramp straight to 100 VUs' but no duration given); `--stage :` (both sides empty after shell quoting mishaps); trailing empty elements from `--stage 1m:5,` style splitting.
Common situations: Modeling instant VU jumps and forgetting k6 expresses them as short durations (e.g. `1s:100`); copy-paste from JSON ramping-vus options where duration defaults existed elsewhere; typos deleting the duration part.
Related errors
- error for stage %d: %w
- 104
- invalid tag, empty name
- invalid tag, empty value
- scenario name can't be empty
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/746ec0cdd946ea67.
Report an issue: GitHub.