> ## Documentation Index
> Fetch the complete documentation index at: https://hitspec.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Upgrading

> Tests that may start failing after an upgrade, why, and how to upgrade safely

Some releases fix checks that used to pass without checking anything, or
settings that were silently ignored. After such an upgrade a test can fail, or
a file can stop loading, even though nothing in your files changed. In every
case listed here the old result was wrong: the new failure is the test finally
checking what it says.

## Upgrade safely

**1. Validate with the new version**

`hitspec validate` reports every file that no longer loads, with
`file:line:col` and a suggested fix, and prints warnings such as unknown
directives. `--strict` turns the warnings into errors and also checks that
every `{{variable}}` is defined. See [validate](/reference/validate-list.md).

```bash theme={null}
hitspec validate tests/ --strict
```

**2. Run the suite once locally**

Run your tests with the new version and read each failure against the
lists below before you touch CI.

```bash theme={null}
hitspec run tests/ --env staging
```

**3. Pin the version in CI**

Upgrade CI on purpose, not as a side effect of a new release. With the
GitHub Action, set `version:`:

```yaml theme={null}
- uses: abdul-hamid-achik/hitspec@v2
  with:
    files: tests/
    version: v2.24.0
```

With Go, install an exact version:
`go install github.com/abdul-hamid-achik/hitspec/v2/cmd/hitspec@v2.24.0`.

Every release lists its changes in the
[changelog](https://github.com/abdul-hamid-achik/hitspec/blob/main/CHANGELOG.md),
and from 2.20.0 on the GitHub release page of each version shows the same
notes.

## 2.24.0

`reporters` and `outputDir` in `hitspec.yaml` used to be read and ignored. They
now apply in `hitspec run`. Nothing changes if your config does not set them,
or sets only `reporters: [console]`.

### `reporters` and `outputDir`

* An unknown reporter name exits 3. Valid names: `console`, `json`, `junit`,
  `tap`, `html`.
* Two or more reporters without `outputDir` exit 3: they would all write to
  stdout. Set `outputDir` or keep one reporter.
* One reporter other than `console` without `outputDir` replaces the console
  report on stdout (`reporters: [json]` is `-o json`).
* With `outputDir`, reports are written there as `hitspec-results.json`,
  `hitspec-junit.xml`, `hitspec-results.tap` and `hitspec-report.html`,
  relative to the config file. If no report goes to stdout, failures are listed
  on stderr as with `--quiet`.
* `-o`, `--output-file`, `HITSPEC_OUTPUT` and `HITSPEC_OUTPUT_FILE` still win
  and skip both keys. See [Configuration](/reference/configuration.md#reporters-and-outputdir).

### Windows

* A connection the server resets before any response exits 4 (network error),
  as on Linux and macOS. It used to exit 1 when Winsock reported the reset.

### hitspec studio

* The stress form has a new last field, `profile`, for a saved stress profile.
  The other fields keep their order.
* Search re-parses a file only when its modification time or size changed, or
  when studio wrote it.

## 2.23.2

* `hitspec run --stress` prints no color codes when stdout is not a
  terminal, as before 2.23.1. Only a check that expected escape codes in a
  file or pipe is affected.
* Go library: `output.WithNoColor(true)` turns colors off for that formatter
  only, not for the whole process.

## 2.23.1

* `hitspec run --metrics ...` without `--stress`, or with `HITSPEC_METRICS`
  set, prints a warning on stderr. It still exits 0. Add `--stress` or unset
  the variable for normal runs.
* studio and `serve` refuse a `__snapshots__` directory or snapshot file that
  a symlink points to outside the workspace. `hitspec run` is unchanged.
* Stress metrics (JSON, Prometheus, DataDog) now include `status_codes`;
  before, they were always empty.
* `hitspec export --exec` runs through `sh` instead of `bash`.

## 2.23.0

2.23.0 runs the whole test suite on Windows and fixes what that found. It
also applies two `hitspec.yaml` keys that used to be ignored. You only need to
change a `.http` file if it relied on one of the old behaviors below.

### Exit codes

* **A busy port exits 3 in every command.** `hitspec mock` and
  `hitspec record` used to exit 1. `hitspec run --stress --metrics prometheus`
  used to print the error and run the whole load test without metrics. It now
  stops before sending a request. See [Exit codes](/reference/exit-codes.md).

### Settings that now apply

* `noColor: true` in `hitspec.yaml` turns colors off in `hitspec run`.
  `--no-color=false` or `HITSPEC_NO_COLOR=false` turns them back on.
* With `verbose: true`, stderr starts with the `config: <path>` line, as with
  `-v`. `--verbose=0` turns both off.

### studio, serve and the desktop app

* A `schema` file that a symlink points to outside the workspace is refused,
  as body files already were. `hitspec run` is unchanged.
* **Apply** on a saved stress profile in the desktop app now sends the
  profile, so its thresholds are checked. A run can report failed thresholds
  where it used to ignore them.
* New JSON fields, omitted when empty: `duration` on the stress status and
  `stress_update` events, `warnings` on history results, and an optional
  `profile` on `POST /api/v1/stress/start`. Strict schema validators must
  allow them.
* In `hitspec studio`, `enter` in search selects the request it found, and
  unsaved edits survive the file being deleted on disk (`ctrl+s` recreates
  it).

### Windows

* Hooks and `>>>shell` blocks run through Git for Windows' `sh` when it is
  installed. Without it they fall back to `cmd /C`, where POSIX syntax does
  not work.
* A contract state handler such as `setup-states.sh` runs through that `sh`.
* Quoted globs (`hitspec run 'tests/**/*.http'`) work.
* studio, `serve` and the desktop app report paths with `/`, and so does
  `hitspec import -o`.
* The MCP server refuses `/x`, `\x` and `C:x` paths.
* On every platform, certificate and key errors show the path in plain
  quotes, without doubled backslashes.

## 2.22.0

Most of 2.22.0 adds things, but some changes show up as a different exit code,
a request sent differently, or a check that used to be switched off. None of
them needs an edit to a `.http` file unless it relied on one of the old
behaviors below.

### Exit codes

* **Network errors exit 4.** A request that gets no response because the
  connection failed (refused, DNS, TLS or certificate errors, a reset before
  any response, a timeout before connecting, an OAuth2 token endpoint or a
  `@waitFor` target that cannot be reached) used to exit 1. If a CI step
  accepts only 0 and 1, treat 4 as a failure too. A timeout after connecting
  and assertion failures are still 1. See [Exit codes](/reference/exit-codes.md).
* **Ctrl+C and SIGTERM exit 130**, after printing the finished requests and
  writing `--output-file`. SIGTERM used to read as 143.
* **Command-line mistakes exit 64**: an unknown flag or command, a bad flag
  value, a missing required flag, the wrong number of arguments, and
  `--dry-run` with `--stress` (which used to run the load test). They exited 1.
* **More configuration errors exit 3 before any request is sent:** a missing
  `--env-file`, a CA, certificate, key or proxy that cannot be loaded, and a
  broken `hitspec.yaml` in `validate`, `studio`, `serve` and `contract` (studio
  and serve used to fall back to the defaults silently).
* **`--dry-run` parses.** A file that does not load exits 2, and filters that
  match exit 0. The plan is always text: `-o` and `--output-file` are ignored.

### Which settings apply

* `hitspec.yaml` is found by walking up from the working directory, stopping
  after the first directory with `.git`, so a config in a parent directory of
  your tests now applies. `hitspec run -v` prints the file it used. Unknown
  keys print a warning; `hitspec validate --strict` turns them into errors.
* `verbose: true` in `hitspec.yaml` now works (it is `-v`); `--verbose=0`
  turns it off.

### Requests sent differently

* **Query lines** (`? name = value`) keep their order and duplicates and encode
  a space as `%20`. They used to be sorted, collapsed and encoded with `+`.
* **Illegal URL characters** (spaces, quotes, non-ASCII) are percent-encoded
  instead of being sent raw.
* **Form blocks** (`& name = value`) keep the spaces in values.
* **A body file that cannot be read** (`< ./file`, a multipart file) fails the
  request. The literal text used to be sent.
* **Unnamed multipart file parts** are sent as field `file`. Name them with
  `file avatar @./me.png`.
* **`@waitFor`** uses the run's client: proxy, CA, `--insecure`, default
  headers and `followRedirects: false` apply to it.
* **Cookies** are only kept between requests with `--cookies` (or
  `cookies: true`).

### Reports and file selection

* Failure output shows the request, the response status and a body excerpt.
  `-v` prints `Response: 200 OK` instead of `Status: 200`; `-vv` adds headers
  and `-vvv` bodies.
* More header names are redacted (anything with `token`, `secret`, `password`
  or `api-key`), in every format including HTML.
* JSON, JUnit and TAP carry new fields such as `warnings`. JUnit states each
  failed assertion once, and TAP escapes backslashes and newlines.
* Directories are searched without `.git`, `node_modules` and `vendor`, and a
  file reached twice runs once.

### mock, record, stress and metrics

* `mock` and `record` listen on `127.0.0.1`; pass `--host 0.0.0.0` for Docker.
  A busy port is an error.
* The Prometheus endpoint listens on `127.0.0.1`; pass `--metrics-host 0.0.0.0`
  when another machine or container scrapes it.
* `mock` answers a wrong method with 405 and serves HEAD for GET routes; CORS
  needs `--cors`.
* `record` redacts tokens, passwords, secrets, API keys and `Set-Cookie` in
  recordings (`--no-redact` keeps them).
* Stress results count requests cut off at the end as incomplete, and
  assertion failures have their own `assertions<N%` threshold, so `rps` and
  `errors` thresholds can move.
* **`errors<0` and `errors: "0%"` are enforced.** They used to switch the
  check off, so a run with errors passed; now one failed request fails it.
* With `--profile`, explicit flags win, and profile thresholds keep their
  operator (`rps: ">=100"`).

### serve, studio and the desktop app

* `--read-only` lets you run requests, stress, mock, record, contract
  verification and import; it only refuses writes to the workspace and
  settings.
* Body files outside the workspace are refused in `serve`, `studio` and the
  desktop app, including in stress and contract runs.
* Starting mock or record on a busy port fails (409 from `serve`), and a
  stress run that cannot start reports `error`.
* `PUT /api/v1/config` applies a key that is present even when it is `0` or
  `""`. Clients that sent those to mean "unchanged" must leave the key out.
* In `hitspec studio`, `enter` no longer submits forms (use `s`, or `ctrl+s`
  while editing), `r` runs the editor's unsaved text, `ctrl+c` cancels a run
  and asks before discarding edits, copy-as fills in `{{variables}}`
  (secrets included), and the mouse is captured only with `--mouse`.

## 2.21.0

### Files that no longer load (exit code 2)

* **Misspelled operators.** They were silently treated as `==`.
  `expect body.name contians "x"` now fails with
  `unknown operator "contians" (did you mean "contains"?)`.

* **An operator with no value, or with two.** `expect status 200 201` is an
  error. To accept several values, use `in`:

  ```http theme={null}
  GET https://api.example.com/health

  >>>
  expect status in [200, 201]
  <<<
  ```

* **Blocks that do not belong to a request.** A `>>>`, `>>>capture`, `>>>db`
  or `>>>shell` block above its request, a block with an unknown type, or a
  block without its closing `<<<` was dropped without a word. Move the block
  below its request, fix the type, or close it.

* **`>>>db <connection>`.** That form never existed, so its checks never ran.
  Set the connection with `# @db` and put the SQL and the column checks inside
  the block (see [Database Testing](/guides/database-testing.md)).

* **Unsupported `jsonpath` features** (`..`, `?()` filters, slices, unions)
  are errors instead of silently matching nothing.

### Assertions that now check what they say

* **null versus absent.** `== null` needs a JSON `null`; a missing field no
  longer equals null. Use `!exists` to check that a field is absent. Value
  operators (`==`, `>`, `contains`, ...) fail on a missing field; the negated
  ones (`!=`, `!contains`, `!includes`, `!in`) pass.

* **Non-JSON bodies.** `body.x` on an HTML or plain-text response fails with
  "response body is not JSON" instead of comparing against nothing.
  `application/problem+json`, other `+json` types and `text/json` count as
  JSON.

* **Unquoted values are read whole.** `expect body.name == Jane Doe` compares
  `Jane Doe`; it used to compare only `Jane`. A trailing `# comment` is no
  longer part of the value.

* **`jsonpath $.x` subjects are evaluated.** They used to resolve to nothing,
  so `!exists` and `!=` passed whatever the response held.

* **Objects and arrays compare by value**, and numbers compare numerically:

  ```http theme={null}
  GET https://api.example.com/users/1

  >>>
  expect body.address == {"city": "Monterrey", "zip": "64000"}
  <<<
  ```

* **`length` counts characters, not bytes.** `"café"` has length 4; it was 5.

* **`contains`, `startsWith`, `endsWith` and `matches`** on a whole JSON body,
  an object or an array use its JSON text.

* **Paths with `.#` and `#(...)` work.** The `#` used to start a comment and
  cut the path short.

* **`# @if` and `# @unless` evaluate `==`, `!=`, `!`, `&&` and `||`.** Before,
  any non-empty text was true, so `# @if {{env}} == prod` ran the request in
  every environment. It is now skipped outside prod.

* **Directives above the first request** of a file without a `###` line were
  dropped. Its `@name`, `@tags`, `@skip` and `@depends` now apply, so tag
  filters and dependencies can select different requests.

* **`>>>db` blocks written as bare SQL** were dropped. They now run, and need
  `--allow-db`.

### Settings that now apply

* **hitspec.yaml in `hitspec run`.** `timeout`, `retries`, `retryDelay`,
  `maxRedirects`, `parallel`, `concurrency` and `bail` were overridden by the
  flag defaults. A yaml `timeout: 5000` now fails requests slower than five
  seconds, `retries: 3` repeats failing requests, and `bail: true` stops at the
  first failure.
* **`HITSPEC_*=false` overrides hitspec.yaml** (`HITSPEC_INSECURE`,
  `HITSPEC_PARALLEL`, `HITSPEC_BAIL`). The order everywhere is: flag, then
  `HITSPEC_*` variable, then hitspec.yaml, then the built-in default.
* **`maxRedirects: N` follows N redirects.** It followed N-1.

### HTTP behavior

* **Proxy variables are honored.** `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY`
  apply when no proxy is configured. On a runner with a proxy in its
  environment, requests now go through it; list hosts that must be reached
  directly in `NO_PROXY`. Loopback hosts are never proxied.
* **Credentials stay on their origin.** On a redirect to another scheme, host
  or port, the headers you set (`Authorization`, `Cookie`, `X-Api-Key`, ...)
  are not forwarded. A test that relied on the credential following the
  redirect gets a 401: point it at the final URL.
* **`Host:` is sent as the request's Host.** It used to be ignored.
* **Compressed bodies are decoded.** With an explicit `Accept-Encoding: gzip`
  or `deflate`, assertions see the decoded body, not compressed bytes.
* **Repeated `Set-Cookie` headers are joined with `, `** (it was `; `), like
  every other repeated header.
* **Response bodies over 100 MiB fail** with "response body exceeds".
* **HTTP/2 is negotiated over TLS.**

### Other commands

* **`hitspec diff`.** Skipped tests have their own status and no longer count
  as failures or regressions. A 0 ms baseline never trips `--threshold`.
* **`hitspec mcp serve`.** Saved requests run by `hitspec_fetch` no longer read
  the server's environment variables; allow one with `--allow-env NAME`.
  `--workspace` refuses your home directory, its parents and `/` unless you
  pass `--allow-broad-workspace`.

## 2.20.0

* **`expect header X exists`** passed even when the header was missing. It now
  fails in that case.
* **A body cut short by a timeout** (`@timeout`, `--timeout`) counted as a
  success with a partial body. It is now a failure. Only `text/event-stream`
  (SSE) responses may end at the deadline.
* **Files that fail to load** (a syntax error, a duplicate `@name`, a circular
  `@depends`) were skipped and the run could still exit 0. `hitspec run` now
  exits 2. `--name` and `--tags` filters that match nothing exit 64. See
  [Exit Codes](/reference/exit-codes.md).
* **`@auth`** with an unknown type or the wrong number of arguments is a parse
  error.
* **The default environment.** Without `--env` or `HITSPEC_ENV`, `hitspec run`
  uses hitspec.yaml `defaultEnvironment` (it always used `dev`), so variables
  such as `{{baseUrl}}` can resolve differently.
* **Header values are sent verbatim.** Quotes and text after `#` are kept:
  `If-None-Match: "abc"` sends the quotes.
* **Captured values are inserted literally.** A `{{...}}` inside a captured
  value is no longer expanded.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.