grafana/k6 · error

both K6_CLOUD_METRICS_PUSH_URL and K6_CLOUD_TEST_RUN_TOKEN…

Error message

both K6_CLOUD_METRICS_PUSH_URL and K6_CLOUD_TEST_RUN_TOKEN must be set together

What it means

When a cloud testRunID is already known (K6_CLOUD_PUSH_REF_ID / config PushRefID set), the output pushes metrics directly to an externally provisioned run instead of creating one. That mode requires both the push URL and the run-scoped token; having exactly one of K6_CLOUD_METRICS_PUSH_URL and K6_CLOUD_TEST_RUN_TOKEN is a misconfiguration and is rejected as a defensive backstop.

Solutions

  1. Set both K6_CLOUD_METRICS_PUSH_URL and K6_CLOUD_TEST_RUN_TOKEN together, or set neither
  2. If k6 should provision the run itself, remove the PushRefID/ref and both scoped push credentials, and authenticate with the normal cloud token (K6_CLOUD_TOKEN)
  3. Audit the provisioning service/env files for the missing half of the pair

Example fix

# before
export K6_CLOUD_TEST_RUN_TOKEN=abc123   # URL missing
k6 run -o cloud script.js
# after
export K6_CLOUD_METRICS_PUSH_URL=https://ingress.grafana.net/...
export K6_CLOUD_TEST_RUN_TOKEN=abc123
k6 run -o cloud script.js
Defensive patterns

Strategy: validation

Validate before calling

// Shell: enforce both-or-neither before running
if [ -n "${K6_CLOUD_METRICS_PUSH_URL:-}" ] || [ -n "${K6_CLOUD_TEST_RUN_TOKEN:-}" ]; then
  : "${K6_CLOUD_METRICS_PUSH_URL:?must be set together with K6_CLOUD_TEST_RUN_TOKEN}"
  : "${K6_CLOUD_TEST_RUN_TOKEN:?must be set together with K6_CLOUD_METRICS_PUSH_URL}"
fi
k6 run -o cloud script.js

Type guard

function pushCredsPaired(env) {
  return (env.K6_CLOUD_METRICS_PUSH_URL != null) === (env.K6_CLOUD_TEST_RUN_TOKEN != null);
}

Prevention

When it happens

Trigger: An external provisioning service injects only K6_CLOUD_TEST_RUN_TOKEN (or only K6_CLOUD_METRICS_PUSH_URL) together with a PushRefID; a hand-written cloud config or env file that sets one of the pair but not the other.

Common situations: Partial secrets injection in CI (token vault populated, URL variable missed); hand-rolled provisioning wrappers that copy config between systems and drop one key.

Related errors


AI-assisted analysis of grafana/k6@8d06114777 (2026-08-18). Data as JSON: /api/errors/a52aa37d7c5a509c. Report an issue: GitHub.

Appendix: source

Thrown at internal/output/cloud/output.go:243

	return nil
}

// Start calls the k6 Cloud API to initialize the test run, and then starts the
// goroutine that would listen for metric samples and send them to the cloud.
func (out *Output) Start() error {
	if out.config.PushRefID.Valid {
		out.testRunID = out.config.PushRefID.String
	}

	if out.testRunID != "" {
		out.logger.WithField("testRunId", out.testRunID).Debug("Directly pushing metrics without init")

		// Exactly one of the scoped push creds set is a misconfiguration.
		// The cmd layer already validates this for the externally-provisioned
		// env case (internal/cmd/outputs_cloud.go); this is the defensive
		// backstop for any other source (e.g. a hand-written cloud config).
		if out.config.MetricsPushURL.Valid != out.config.TestRunToken.Valid {
			return errors.New(
				"both K6_CLOUD_METRICS_PUSH_URL and K6_CLOUD_TEST_RUN_TOKEN " +
					"must be set together")
		}

		// Provisioning-mode push. Armed whenever the scoped push credentials
		// are present: either k6 self-provisioned (no PushRefID) or an external
		// service provisioned the run and its creds were injected into the
		// cloud config by the cmd layer (PushRefID set; that service owns
		// create/start/notify).
		if out.config.MetricsPushURL.Valid && out.config.TestRunToken.Valid {
			if err := out.lazyInitProvisioning(); err != nil {
				return err
			}
		}

		return out.startVersionedOutput()
	}

View on GitHub (pinned to 8d06114777)