Which k6 threshold keys are rejected before the run starts, and which malformed-looking ones are accepted?
answer
- shape is checked, meaning is not
- closing brace must end the key
- every pair needs a value
- only the first colon splits a pair
basics
~20 sk6 rejects a threshold key with unmatched braces, a brace that is not final, a pair lacking a value after its colon, or an unknown metric name. It accepts whitespace, quotes, colons inside values, and tag keys nothing emits.
solid answer
~40 sk6 parses the key by taking the first `{` and the last `}`, requiring the closing brace to be the final character, then splitting on commas and requiring each pair to have something after its first colon. `{name}`, `{status:}`, `{}` and an unmatched or non-final brace are all invalid configuration and stop the run, as is a metric name in front of the brace that no built-in or script-declared metric owns. Everything else is tolerated: whitespace and one layer of quotes are stripped, only the first colon splits a pair - so `group_duration{group:::checkout}` and a full URL as a value are legal - and a tag key nothing ever emits is accepted silently. A value can never contain a comma.
go deeper
Recall the shape k6 expects: metric name, then braces at the very end holding comma-separated key:value pairs, each with something after the colon.
Explain that k6 checks shape and the metric name only, that the first colon splits a pair so values may contain colons and slashes, and that a comma can never appear in a value.
Know which mistakes stop the run and which slip through, and treat a parsing key as no evidence at all that the selector matches the samples you meant.
Weigh how much of a suite's verdict rests on hand-written strings that k6 checks only for shape, and decide what review or smoke check compensates for that.
## Two different checks on one string A k6 threshold key such as `http_req_duration{status:200}` is checked twice on the way to a running test, and confusing the two is what makes its error messages surprising. The first check is **shape**. k6 finds the first `{` and the last `}` in the key, requires both to be present and the closing brace to be the final character, and then splits the text between them on commas, requiring each fragment to contain a colon with something after it. The second check is **existence**, and it applies to the metric name in front of the brace only: k6 looks it up in the metric registry, which by then holds the built-in metrics and any metric the script declared in its init context. Both checks happen while the config is being consolidated, so both failures stop the run before a single VU starts, as an invalid configuration. Nothing checks the *meaning* of the tags. There is no list of legal tag keys - custom tags are arbitrary strings your script invents - so any well-formed pair is accepted and simply produces a sub-metric that may or may not ever match a sample. ## Keys k6 rejects before the test starts | key | why it is rejected | |---|---| | `http_req_duration{name}` | the pair has no colon and no value | | `http_req_duration{status:}` | the pair has a colon but an empty value | | `http_req_duration{status:200,method:}` | one malformed pair in a list is enough | | `http_req_duration{status:200` | opening brace with no closing brace | | `http_req_duration status:200}` | closing brace with no opening brace | | `http_req_duration{status:200}extra` | the closing brace is not the last character | | `http_req_duration{}` | empty selection criteria | | `htp_req_duration{status:200}` | no metric of that name exists | The last row is worth separating from the rest: it is the only one that is about your *program* rather than about the string. Its message reads *no metric name "..." found*, and it names the whole expression, braces included, even though only the part in front of the brace was looked up. ## Keys k6 accepts that look as though it should not - **Whitespace anywhere around a key or a value.** `{ status : 200 }` is trimmed down to the pair `status` / `200`. - **One layer of single or double quotes on either side.** `{status:"200"}` and `{status:'200'}` build the same tag set as `{status:200}`. Quotes are how you keep leading or trailing spaces in a value, since everything unquoted is trimmed. - **Colons inside a value.** Only the first colon in a pair splits it, so `group_duration{group:::checkout}` gives the tag `group` the value `::checkout`, which is exactly the shape k6's own group paths take, and `http_req_duration{name:http://example.com:8080/cart}` gives `name` a complete URL. - **Braces inside a value.** The parser takes the first `{` and the *last* `}`, so a templated URL such as `{name:http://${}.com}` survives. - **A tag key nothing will ever emit.** `{endpont:checkout}` is well formed, so the run starts and the sub-metric stays empty. - **A repeated key.** `{status:200,status:201}` applies the pairs in order and keeps the last, silently ending up with `status` set to `201`. The one thing a value can never contain is a **comma**, because the comma split runs before anything else. A tag value with a comma in it is unreachable by a threshold selector. ## The collision nobody expects Because whitespace and quotes are normalised and pair order is irrelevant, several different-looking keys resolve to the same tag set - and k6 reuses a sub-metric when its tag set already exists. Declare both `'http_req_duration{status:200}'` and `'http_req_duration{status: "200"}'` in the same `thresholds` object and they are two distinct keys in the map but one sub-metric underneath, so one rule list overwrites the other. Which one survives depends on map iteration order, so the outcome is not even stable between runs. The same happens for `{a:1,b:2}` against `{b:2,a:1}`. ## The practical takeaway Treat the string as a grammar with three rules worth memorising: braces must wrap the very end of the key, every pair needs a non-empty value after its first colon, and commas separate pairs and nothing else. Everything the grammar accepts, k6 will run with - including selectors that can never match. Rejection is not evidence that the surviving keys are right, only that they parse.
- Why is group_duration{group:::checkout} valid rather than a typo?k6 splits a pair on its first colon only, so the key is `group` and the value is `::checkout`. That leading double colon is part of the value: k6 builds a group's full path by joining names with `::` from an unnamed root, so a top-level group named checkout has the path `::checkout`.
- What happens if two threshold keys normalise to the same selector?They collide. `{status:200}` and `{status: "200"}` build an identical tag set, and k6 reuses an existing sub-metric rather than creating a second one, so one key's rule list overwrites the other's. Which survives depends on map iteration order, so it is not stable between runs.
- Can a threshold select on a tag value that contains a comma?No. k6 splits the text between the braces on commas before it looks at colons, so a value containing a comma is parsed as two pairs and can never be expressed. Choose a tag value without commas, or scope on a different tag.
saying these in an interview costs you the question
- Believing k6 validates tag keys against a list of known tags
- Thinking a colon inside a value breaks the key
- Expecting quotes around a value to change what it matches
- Assuming a key that parses will therefore match samples
- Treating trailing characters after the closing brace as harmless