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

  1. Always prefix a duration: `--stage 1s:100` for a near-instant jump to 100 VUs
  2. Check the flagged index (0-based) among your --stage occurrences and add the missing 'Nm:' prefix
  3. For plateau-then-drop patterns, spell each stage explicitly: `--stage 1m:100 --stage 1m:100`
  4. 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

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


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/746ec0cdd946ea67. Report an issue: GitHub.