k6 `v2.0.0-rc1` is here 🎉!

This release marks the first release candidate for k6 v2.0.0 — a major version that completes a long-running cleanup of deprecated APIs, old commands, and obsolete configuration options. Like the v1.0.0-rc1 before it, the purpose of this release is to give the community a chance to test the upgrade path, identify any issues, and migrate scripts or workflows affected by breaking changes. If you encounter any problems, please [report them](https://github.com/grafana/k6/issues).

Here's a glimpse of what's changed in this release:

- The Go module path has changed to `go.k6.io/k6/v2` — all extensions must update their import paths to be compatible with v2.
- Removal of all long-deprecated CLI commands and flags: `k6 login`, `k6 pause`, `k6 resume`, `k6 scale`, `k6 status`, `--no-summary`, `--upload-only`, and more.
- The `externally-controlled` executor has been removed — scripts using `executor: externally-controlled` will no longer run.
- Cloud run non-threshold aborts (aborted by user, system, timeout, etc.) now return exit code `97` instead of `0`.
- `options.ext.loadimpact` is no longer supported — use `options.cloud`.
- `k6/experimental/redis` module has been removed.
- The `k6 cloud` command now shows help by default instead of attempting to run.
- A stack is now required for all `k6 cloud` commands — the previous fallback to the first available stack has been removed.
- The web-vitals library has been updated to v5.1.0, removing the deprecated FID metric.

## Breaking changes

These are changes that require you to update your scripts, CI/CD pipelines, or configuration files before upgrading.

### Go module path changed to `go.k6.io/k6/v2` [#5777](https://github.com/grafana/k6/pull/5777)

Following the [Go module versioning conventions](https://go.dev/ref/mod#major-version-suffixes), the k6 module path has changed from `go.k6.io/k6` to `go.k6.io/k6/v2`.

Any extension or external package that imports `go.k6.io/k6` must update all import paths to `go.k6.io/k6/v2`. For the vast majority of extensions this is the only change needed — a mechanical find-and-replace across the codebase:

```
go.k6.io/k6/ → go.k6.io/k6/v2/
```

For example:

```go
// Before
import "go.k6.io/k6/js/modules"

// After
import "go.k6.io/k6/v2/js/modules"
```

As a result, **no existing extensions will be compatible with v2.0.0-rc1**. As we move toward the final v2.0.0 release, we expect most extensions to either already support the new path or to do so shortly after.

### Removed CLI commands [#5653](https://github.com/grafana/k6/pull/5653)

The following commands for controlling a running test have been removed. They have not been functional for most use cases since the REST API they relied on was limited to specific execution modes:

- `k6 pause`
- `k6 resume`
- `k6 scale`
- `k6 status`

**Migration:** There is no replacement. These commands relied on the `externally-controlled` executor, which has also been removed in v2.0.0 (see below).

### Removed `externally-controlled` executor [#5846](https://github.com/grafana/k6/pull/5846)

The `externally-controlled` executor has been removed. It was legacy code from an older k6 Cloud architecture that allowed external systems to scale VUs and pause/resume a running test via the k6 REST API — the capability that `k6 pause`, `k6 resume`, `k6 scale`, and `k6 status` relied on.

**Migration:** There is no replacement. Any test script with `executor: externally-controlled` will fail to start. Migrate to a different executor based on the desired load profile (e.g., `ramping-vus`, `constant-vus`, `constant-arrival-rate`).

### Removed `k6 login` command [#5134](https://github.com/grafana/k6/pull/5134)

The top-level `k6 login` command and its subcommands (`k6 login cloud`, `k6 login influxdb`) have been removed.

**Migration:**
- Replace `k6 login cloud` → `k6 cloud login`
- InfluxDB authentication is no longer configurable via a login command. Use environment variables such as `K6_INFLUXDB_*` to configure the InfluxDB output directly.

### `k6 cloud script.js` no longer runs a test [#5624](https://github.com/grafana/k6/pull/5624)

Running `k6 cloud` without an explicit subcommand (i.e., the old positional-argument form `k6 cloud script.js`) now shows the help output instead of attempting to run the test. The `run` subcommand was already the recommended path since `k6 cloud run` was introduced.

**Migration:** Replace `k6 cloud script.js` with `k6 cloud run script.js`.

### Removed `--upload-only` flag [#5844](https://github.com/grafana/k6/pull/5844)

The `--upload-only` flag on the `k6 cloud` command has been removed.

**Migration:** Use `k6 cloud upload script.js` to upload a test without running it.

### Removed `--no-summary` flag [#5729](https://github.com/grafana/k6/pull/5729)

The `--no-summary` flag has been removed.

**Migration:** Replace `--no-summary` with `--summary-mode=disabled`.

### Removed `--summary-mode=legacy` [#5730](https://github.com/grafana/k6/pull/5730)

The `legacy` value for `--summary-mode` has been removed.

**Migration:** There is no direct equivalent — the new summary format is different from the legacy one. Review the available [summary modes](https://grafana.com/docs/k6/latest/results-output/end-of-test/#summary-modes) and choose the one that best fits your needs: `compact` (the default) or `full` for more detailed output.

### Removed `options.ext.loadimpact` support [#5774](https://github.com/grafana/k6/pull/5774)

The `options.ext.loadimpact` configuration block in test scripts is no longer supported.

**Migration:** Move all cloud-related configuration from `options.ext.loadimpact` to `options.cloud`:

```javascript
// Before
export const options = {
  ext: {
    loadimpact: {
      projectID: 12345,
      name: "My Test",
    },
  },
};

// After
export const options = {
  cloud: {
    projectID: 12345,
    name: "My Test",
  },
};
```

### Removed `k6/experimental/redis` module [#5485](https://github.com/grafana/k6/issues/5485)

The `k6/experimental/redis` module has been removed from the k6 core binary. It was shipped as an experiment and has not been promoted to stable.

**Migration:** Change your import from `k6/experimental/redis` to `k6/x/redis`. With [auto-extension-resolution](https://grafana.com/docs/k6/latest/using-k6/auto-extension-resolution/), k6 will automatically provision the [xk6-redis](https://github.com/grafana/xk6-redis) extension when it sees the `k6/x/redis` import.

### Removed `ExporterType` option from OpenTelemetry output [#5754](https://github.com/grafana/k6/pull/5754)

The deprecated `exporterType` configuration option for the OpenTelemetry output has been removed.

**Migration:** Replace `K6_OTEL_EXPORTER_TYPE` with `K6_OTEL_EXPORTER_PROTOCOL`. The accepted values are `grpc` and `http/protobuf`.

### Removed `SingleCounterForRate` option from OpenTelemetry output [#5830](https://github.com/grafana/k6/pull/5830)

The temporary `SingleCounterForRate` escape-hatch option for the OpenTelemetry output has been removed. It was introduced as a one-release migration aid in [#5164](https://github.com/grafana/k6/pull/5164) to let users revert to the old pair-of-counters format (`<metric>.occurred` + `<metric>.total`) while upgrading. Rate metrics are now always exported as a single counter with a `condition` attribute (`nonzero`/`zero`).

**Migration:** Remove any `K6_OTEL_SINGLE_COUNTER_FOR_RATE=true` configuration. If you were using the old pair-of-counters format, update your dashboards and queries to use the single counter with `condition` attribute instead.

### Removed `K6_BINARY_PROVISIONING` environment variable [#5734](https://github.com/grafana/k6/pull/5734)

The `K6_BINARY_PROVISIONING` environment variable, deprecated in v1.2.0, has been removed.

**Migration:** Remove `K6_BINARY_PROVISIONING` from your environment. Auto-extension-resolution is enabled by default; `K6_AUTO_EXTENSION_RESOLUTION` only needs to be set explicitly if you want to disable it.

### Removed `K6_ENABLE_COMMUNITY_EXTENSIONS` environment variable [#5733](https://github.com/grafana/k6/pull/5733)

The `K6_ENABLE_COMMUNITY_EXTENSIONS` environment variable has been removed. The community and cloud extension catalogs were merged server-side, making this flag a no-op since the catalogs were unified.

**Migration:** Remove `K6_ENABLE_COMMUNITY_EXTENSIONS` from your environment. Community extensions are now resolved through the default build service URL automatically.

### Stack is now required for all `k6 cloud` commands [#5833](https://github.com/grafana/k6/pull/5833)

Providing a stack is now mandatory for all `k6 cloud` commands (`k6 cloud run`, `k6 cloud upload`, `k6 cloud run --local-execution`). Previously, omitting a stack would fall back to the first available stack with a deprecation warning — that fallback has been removed. Likewise, `k6 cloud login` now requires both a token and a stack; passing one without the other fails with an explicit error.

**Migration:** Run `k6 cloud login` which will ask you for the stack and setup correctly for the new version of k6. Alternatively, the `K6_CLOUD_STACK_ID` environment variable, or the `stackID` script option are available to be set before running any `k6 cloud` command .

### Removed legacy configuration file path migration [#5609](https://github.com/grafana/k6/pull/5609)

k6 no longer automatically migrates configuration files from the old `{USER_CONFIG_DIR}/loadimpact/config.json` path introduced before k6 v1.0.0.

**Migration:** If you still have a config file at the old path, move it to `{USER_CONFIG_DIR}/k6/config.json`. You can also re-run `k6 cloud login` to regenerate the file at the correct location.

### Cloud run non-threshold aborts now exit with code 97 [#5769](https://github.com/grafana/k6/pull/5769)

Previously, most cloud abort scenarios returned an exit code of `0`, making it impossible for CI pipelines to distinguish a failed run from a successful one. These scenarios now return exit code `97`.

| Scenario | Before | After |
| --- | --- | --- |
| Finished | `0` | `0` |
| Aborted by threshold | `99` | `99` |
| Aborted by system, limit, script error, user, or timeout | `0` | `97` |

**Migration:** Review any CI pipeline logic that treats exit code `0` as success for cloud runs. Cloud runs that abort for reasons other than threshold violations will now return exit code `97`.

### Browser: FID metric removed, web vitals updated to v5.1.0 [#5661](https://github.com/grafana/k6/pull/5661)

The `browser_web_vital_fid` metric has been removed. FID (First Input Delay) was deprecated as a Core Web Vital and replaced by INP (Interaction to Next Paint). The embedded web-vitals library has been updated from v3 to v5.1.0, which reflects this change.

**Migration:** Update any dashboards, alerts, or threshold rules that reference `browser_web_vital_fid` to use `browser_web_vital_inp` instead.

## New features

### Cloud secret source [#5738](https://github.com/grafana/k6/pull/5738)

A new built-in secret source, `cloud`, allows k6 scripts to retrieve secrets stored in Grafana Cloud directly during local execution. Pass `--secret-source=cloud` when running `k6 cloud run --local-execution` to make cloud-managed secrets available to your script via `secrets.get()`. Thanks, @vortegatorres!

```shell
k6 cloud run --local-execution --secret-source=cloud script.js
```

### Extension tab-completion provisioning [#5761](https://github.com/grafana/k6/pull/5761)

When using shell tab-completion, pressing TAB after a fully-typed extension name (e.g. `k6 x docs <TAB>`) will now automatically provision a custom binary with that extension and delegate the completion request to it. This means extension-specific flags and subcommands are available via tab-completion without any manual setup.

### Pre-manifest extension dependencies captured in archive metadata [#5819](https://github.com/grafana/k6/pull/5819)

When creating a k6 archive (`k6 archive`), extension dependency information is now captured in `metadata.json` under a `"dependencies"` field, using the constraints declared by the script before any external manifest overrides are applied. This ensures that `k6/x/` imports are preserved correctly during [auto-extension-resolution](https://grafana.com/docs/k6/latest/using-k6/auto-extension-resolution/) re-execution.

## UX improvements and enhancements

- [#5815](https://github.com/grafana/k6/pull/5815) Warns when `http.get()` or `http.head()` receive extra arguments that are silently ignored, helping catch common scripting mistakes. Thanks, @moko-poi!
- [#5657](https://github.com/grafana/k6/pull/5657) Adds automatic retries to `newAction`-based Locator APIs in the browser module, improving reliability of browser tests. Thanks, @janHildebrandt98!
- [#5779](https://github.com/grafana/k6/pull/5779) Adds a `k6 x docs` hint to the main k6 help output for built-in documentation discovery. Thanks, @Reranko05!
- [#5788](https://github.com/grafana/k6/pull/5788) Adds a `k6 x explore` hint to the main k6 help output for extension discovery.

## Bug fixes

- [#5855](https://github.com/grafana/k6/pull/5855) Fixes auto-extension-resolution incorrectly triggering binary provisioning when a script manifest specifies a dependency as a Go module pseudo-version (`v0.0.0+sha` or `v0.0.0-timestamp-sha`), even when the running binary already satisfied the constraint.
- [#5769](https://github.com/grafana/k6/pull/5769) Fixes several `k6 cloud run` TUI issues: ghost duplicate progress bars appearing after the run finishes, a timer that stalled and then jumped to 100%, and a stale timer racing with the status line.
- [#5814](https://github.com/grafana/k6/pull/5814) Fixes `k6 cloud run --local-execution` ignoring `K6_CLOUD_PUSH_REF_ID` and unconditionally creating a new test run instead of reusing the provided run ID. Thanks, @Reranko05!
- [#5716](https://github.com/grafana/k6/pull/5716) Fixes WebSocket `bufferedAmount` not being incremented when sending TypedArrays, causing it to go negative. Thanks, @prakharbirla-ng!
- [#5630](https://github.com/grafana/k6/pull/5630) Fixes duplicate redirect request metric emissions in the browser module, where each redirect was incorrectly emitting metrics for all prior redirects in the chain.
- [#5786](https://github.com/grafana/k6/pull/5786) Fixes swapped Min and Max values for Gauge metrics in Cloud output v2, which caused incorrect peak and floor values in cloud test result queries. Thanks, @esquonk!
- [#5785](https://github.com/grafana/k6/pull/5785) Fixes a race condition in the browser module's `handleExitEvent` where `Done()` was signalled before the subscription was removed, causing tests to hang until the timeout.
- [#5574](https://github.com/grafana/k6/pull/5574) Fixes position parser for base pointer options. Thanks, @chrismooreproductions!

## Maintenance and internal improvements

- [#5847](https://github.com/grafana/k6/pull/5847) Moves `TaskQueue` from the external `github.com/mstoykov/k6-taskqueue-lib` into `internal/js/taskqueue`, eliminating an awkward reverse dependency where the library's own tests depended on k6.
- [#5775](https://github.com/grafana/k6/pull/5775) Moves xk6-dashboard source code into `internal/dashboard`, making the web dashboard a built-in part of the k6 binary without requiring a separate extension. The dashboard is available via `k6 run --out=web-dashboard`.
- [#5703](https://github.com/grafana/k6/pull/5703), [#5818](https://github.com/grafana/k6/pull/5818) Uses Go's `synctest` package within `lib/executor`, eventloop, and timer tests for virtualized time, improving test determinism.
- [#5778](https://github.com/grafana/k6/pull/5778) Bumps Ubuntu CI runners to 24.04 and always installs Chromium for browser tests.
- [#5826](https://github.com/grafana/k6/pull/5826), [#5827](https://github.com/grafana/k6/pull/5827) Updates `go.opentelemetry.io/otel/exporters/otlp/otlptracehttp` and `otlpmetrichttp` to `v1.43.0` [security], adding a 4 MiB response body cap to mitigate memory exhaustion from misconfigured or malicious servers.
- [#5669](https://github.com/grafana/k6/pull/5669) Updates `go.opentelemetry.io/otel` packages.
- [#5740](https://github.com/grafana/k6/pull/5740), [#5796](https://github.com/grafana/k6/pull/5796) Updates `github.com/grafana/sobek` digest.
- [#5783](https://github.com/grafana/k6/pull/5783), [#5838](https://github.com/grafana/k6/pull/5838), [#5862](https://github.com/grafana/k6/pull/5862) Updates `github.com/grafana/k6-cloud-openapi-client-go`.
- [#5407](https://github.com/grafana/k6/pull/5407), [#5801](https://github.com/grafana/k6/pull/5801) Updates `github.com/mailru/easyjson` to `v0.9.2`.
- [#5616](https://github.com/grafana/k6/pull/5616), [#5805](https://github.com/grafana/k6/pull/5805) Updates `github.com/evanw/esbuild` to `v0.28.0`.
- [#5667](https://github.com/grafana/k6/pull/5667) Updates golangci-lint.
- [#5799](https://github.com/grafana/k6/pull/5799) Updates `buf.build/gen/go/prometheus` protobuf.
- [#5800](https://github.com/grafana/k6/pull/5800) Updates `github.com/andybalholm/brotli` to `v1.2.1`.
- [#5804](https://github.com/grafana/k6/pull/5804) Updates `examples/` dependencies.
- [#5806](https://github.com/grafana/k6/pull/5806) Updates `github.com/fatih/color` to `v1.19.0`.
- [#5807](https://github.com/grafana/k6/pull/5807) Updates `github.com/puerkitobio/goquery` to `v1.12.0`.
- [#5840](https://github.com/grafana/k6/pull/5840) Updates `github.com/mattn/go-isatty` to `v0.0.21`.
- [#5857](https://github.com/grafana/k6/pull/5857) Updates Go to `v1.25.9`.
- [#5863](https://github.com/grafana/k6/pull/5863), [#5880](https://github.com/grafana/k6/pull/5880) Updates `github.com/grafana/k6provider` to `v0.4.0`.
- [#5670](https://github.com/grafana/k6/pull/5670) Updates GitHub artifact actions (major).
- [#5693](https://github.com/grafana/k6/pull/5693) Updates `crazy-max/ghaction-chocolatey` to `v4`.
- [#5706](https://github.com/grafana/k6/pull/5706) Updates `actions/setup-go` to `4a36011`.
- [#5707](https://github.com/grafana/k6/pull/5707) Updates `anchore/sbom-action` to `v0.24.0`.
- [#5718](https://github.com/grafana/k6/pull/5718), [#5861](https://github.com/grafana/k6/pull/5861) Updates `github/codeql-action` to `v4.35.2`.
- [#5720](https://github.com/grafana/k6/pull/5720), [#5802](https://github.com/grafana/k6/pull/5802) Updates `docker/login-action` to `v4.1.0`.
- [#5771](https://github.com/grafana/k6/pull/5771), [#5798](https://github.com/grafana/k6/pull/5798) Updates `grafana/shared-workflows` actions.
- [#5836](https://github.com/grafana/k6/pull/5836) Updates `actions/upload-artifact`.
- [#5809](https://github.com/grafana/k6/pull/5809), [#5839](https://github.com/grafana/k6/pull/5839) Updates the Docker base image tags (Debian to v13, Go to `v1.26.2`).
- [#5872](https://github.com/grafana/k6/pull/5872) Fixes v6 cloud client losing the request body when retrying after a `Connection: close` response.
- [#5871](https://github.com/grafana/k6/pull/5871) Returns an accurate error from the v6 cloud client when a test name lookup returns no results.
- [#5751](https://github.com/grafana/k6/pull/5751) Updates `github.com/klauspost/compress` to `v1.18.5`.
- [#5841](https://github.com/grafana/k6/pull/5841) Updates `golang.org/x` packages.
- [#5860](https://github.com/grafana/k6/pull/5860) Updates Alpine Docker image to `v3.23.4`.

## External contributors

A huge thank you to the external contributors who helped during this release: @moko-poi, @janHildebrandt98, @Reranko05, @prakharbirla-ng, and @chrismooreproductions! 🙏
