A k6 VU logs in on its first iteration but is unauthenticated on the next. Why?
answer
- the login worked, the carrier did not
- something is emptied each iteration
- cookies reset, variables do not
- one root option turns it off
- no CLI flag, env var only
basics
~20 sk6 replaces each VU's cookie jar with an empty one at the start of every iteration, so a session cookie set on iteration 0 is gone on iteration 1. Set noCookiesReset to true, or keep the credential in a module-scope variable.
solid answer
~40 sThe login succeeded; what vanished is the cookie carrying the session. k6 gives every VU its own HTTP cookie jar and, by default, **replaces that jar with an empty one at the start of each iteration**, so a `Set-Cookie` from iteration 0 never reaches iteration 1. The signature is distinctive: the first iteration of every VU passes and all later ones fail. There are two clean fixes. Set the root option `noCookiesReset: true` and the jar lives for the VU's whole life, or stop relying on cookies at all and cache the credential in a module-scope variable, which is init-context state and is never reset between iterations. You cannot move the login into init code: k6 forbids HTTP requests there.
code
javascript · 17 linesimport http from 'k6/http';
// Module scope: survives every iteration this VU runs.
let token = null;
export const options = { vus: 2, iterations: 10 };
export default function () {
if (token === null) {
// The cookie jar would be empty again next iteration; the token is not.
const res = http.post('https://quickpizza.grafana.com/api/users/token/login', '{}');
token = res.json('token');
}
http.get('https://quickpizza.grafana.com/api/headers', {
headers: { Authorization: `Bearer ${token}` },
});
}go deeper
Remember the default: k6 empties each virtual user's cookie jar at the start of every iteration. A session cookie from one iteration is simply not there in the next.
Be able to name both halves of the contrast. Cookies are reset at the iteration boundary; module-scope variables are not, because init code runs once per VU and is never re-run.
Describe how you would recognise it from the run output: first iterations pass, later ones fail, failure count scales with iterations rather than VUs, and setting noCookiesReset: true makes the symptom disappear.
The real decision is what a VU is meant to represent. A long-lived session and a fresh session per iteration are different workloads, so pick one deliberately and make the script say which, rather than inheriting the default by accident.
## What actually happened The login worked. What disappeared is the cookie that carried the session. k6 gives every VU its own HTTP cookie jar, and by default it **replaces that jar with an empty one at the start of every iteration**. So a `Set-Cookie` the server sent during iteration 0 is stored, used for the rest of that iteration, and then thrown away before iteration 1 begins. The next request goes out with no `Cookie` header, the server treats it as an anonymous caller, and the script reports a 401 or a redirect to the login page from the *second* iteration onwards while the first one looked perfect. This is a deliberate default, not a bug: without it, one VU's iterations would each start from a different amount of accumulated session state and would stop being comparable to one another. ## What resets and what does not | State | Reset between a VU's iterations? | | --- | --- | | the VU's HTTP cookie jar | **yes**, by default | | a module-scope `let` or `const` | no -- init code is not re-run | | `exec.vu.iterationInInstance` | no, it increments | | local variables inside `default` | yes, they are recreated | That table is the whole answer to "what survives an iteration boundary in k6". Two things that both look like "the VU's session" have opposite lifetimes, which is exactly why this bug is so easy to write and so confusing to read. ## The three ways to fix it 1. **Turn the reset off.** Set the root option `noCookiesReset: true` and the jar lives for the VU's whole life. It defaults to `false`, can also be supplied as the `K6_NO_COOKIES_RESET` environment variable, and has **no CLI flag**. This is the smallest change and the right one when your model of a virtual user really is "one long-lived browser session". 2. **Keep the credential in per-VU state instead of in the jar.** Declare `let token = null` at module level, log in on the iteration that finds it `null`, and send an `Authorization` header yourself on every request. The variable is init-context state, so it survives every iteration without touching cookie behaviour at all. This is the usual shape for token-based APIs. 3. **Log in every iteration on purpose.** Sometimes that is the workload you meant to describe. If so, keep the default and make the login an explicit, measured step rather than an accident. ## What you cannot do You cannot move the login into the init context. k6 forbids HTTP from init code outright -- calling `http.post()` there fails with *Making http requests in the init context is not supported* -- so "just authenticate once at the top of the file" is not available. The nearest built-in equivalent is `setup()`, which runs once for the whole test and whose return value is handed to every iteration; that gives one shared credential rather than one per VU, which may or may not be what your scenario means. ## Diagnosing it from the outside The signature is distinctive once you have seen it: - iteration 0 succeeds for every VU and later iterations fail; - the failure count scales with total iterations, not with VU count; - `http_req_duration` looks healthy because the 401s are fast; - adding `noCookiesReset: true` makes the symptom vanish entirely, which confirms the diagnosis even if you then choose a different fix. If instead the *first* iteration also fails, the problem is the login itself, not the reset. ## The scope question nobody asks but should `noCookiesReset: true` does **not** create a shared jar. Each VU still has its own; the option only stops k6 from replacing that VU's jar between that VU's own iterations. Two VUs never see each other's cookies under any setting, which is consistent with everything else about per-user state in k6: state belongs to a VU, and the only cross-VU sharing k6 offers is read-only. ## Version note This behaviour and the `noCookiesReset` option are current in k6 v2.x. The reset also applies to the throwaway VUs k6 uses for `setup()` and `teardown()`, so a cookie obtained in `setup()` is not carried into the load phase either -- return the value you need instead.
- Can you avoid the reset by logging in once in a k6 script's init context?No. k6 rejects HTTP from init code with *Making http requests in the init context is not supported*. The nearest built-in equivalent is `setup()`, which runs once for the whole test and passes its return value to every iteration, but that gives one shared credential rather than one per VU.
- Does `noCookiesReset: true` share cookies between k6 VUs?No. Each VU still has its own jar; the option only stops k6 from replacing that VU's jar between that VU's own iterations. Two VUs never see each other's cookies under any setting, because cookie state belongs to the VU.
- Where do you set `noCookiesReset`, and what is its default?It is a root k6 option: `noCookiesReset: true` in the exported `options` object or a config file, or the `K6_NO_COOKIES_RESET` environment variable. It defaults to `false`, and unlike most options it has no CLI flag of its own.
saying these in an interview costs you the question
- Assumes k6 keeps cookies across iterations the way a browser does
- Concludes the login endpoint is flaky rather than that the jar was emptied
- Believes noCookiesReset makes one cookie jar shared between VUs
- Tries to log in in k6's init context, where HTTP is not allowed
- Expects a module-scope token to be cleared along with the cookies