Skip to main content
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.
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.
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::
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, 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.

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.

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.
  • 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:
  • 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).
  • 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:
  • 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.
  • @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.
Last modified on October 19, 2026