Skip to content

Test your API

Compare environments

Since 0.4.0 Requires Cloak Proxy 0.4.0 or later.

Send captured requests to two environments — or compare what production returned with what staging returns now — and see per request whether the status, the fields, their types or their values differ, with timestamps and request IDs ignored. Mirror live traffic to a second host with Map Remote's mirror mode. MCP and CI included.

“Does staging behave like production?” usually means two exports and a diff. Compare Environments answers it from the traffic you’ve already captured: each request is sent to the other environment (or to both), and the two answers are compared field by field — status, fields only on one side, types and values — with the fields that change on every call left out.

It has two modes:

  • Run Against Both replays captured requests against two environments, or against one and the responses you captured.
  • Mirror Live Traffic copies live requests to a second host as the app runs, with a Map Remote rule in mirror mode, and compares each copy’s answer with the original’s.

Each captured request against staging, worst first. The detail pane lists the fields that differ.

  1. Run against both, or mirror live traffic
  2. Compare with the captured responses, or send to a left environment too
  3. Where the right side goes, and headers it replaces
  4. The result for each request
  5. The fields that differ
The Compare Environments window comparing Production (the captured responses) with Staging (https, api.staging.shopapp.example, one header replaced). The list shows GET /v2/recommendations with 200 in production and 503 in staging and GET /v2/stores/{id} with 200 and 404, both Status differs; GET /v2/products and GET /v2/users/me as Shape differs; GET /v2/addresses and GET /v2/config as Values differ; GET /v2/cart, /v2/orders/{id} and /v2/products/{id} as Same; and four POST requests not sent. The selected GET /v2/products shows Shape differs, 89% alike, both sides' status and time, and three differences: $.items[0].price, $.items[1].price and $.items[2].price, each a number in production and a string in staging.
  1. Capture the requests you want to compare — a journey through the app, or your end-to-end tests — against one environment (say production).

  2. Open Tools › Compare Environments…, or select requests, right-click and choose Compare with Another Environment…, or right-click a host in the sidebar and choose Compare it with Another Environment…

  3. Set up the two sides:

    • Left: leave Captured on to compare with the responses you captured, or turn it off and give a scheme, host and port to send to a second environment too.
    • Right: the environment to compare with — its scheme, host (such as api.staging.example.com) and port. Empty fields keep the captured value.
    • Headers… on either side replaces headers for that environment, one per line as Name: value, such as its own Authorization token. A name with no value removes the header.

    Click a side’s name to rename it (Production, Staging).

  4. Choose the Scope — the selection, an app, a host, or all captured traffic — and click Run (⌘ ↩).

Each request is sent straight to the servers, not through Cloak Proxy’s tools and not into the traffic list. By default:

  • one request per endpoint is sent (/orders/42 and /orders/57 are one endpoint);
  • POST, PUT, PATCH and DELETE requests aren’t sent, as they change data — they’re listed as Not sent;
  • static files, tunnels and CORS preflights are left out;
  • four requests run at once, at most 10 per second per side.

The window’s subtitle sums up the run: how many requests were sent and how many differ. Each row shows the request, both sides’ status and time, how alike the two bodies are, and how many fields differ:

Result Means
Status differs The two sides returned different status codes, such as 200 and 503
Error One side didn’t answer: it refused the connection, timed out, or the TLS handshake failed
Shape differs A field is only on one side, or has a different type (a number on one side, a string on the other)
Values differ The same fields and types, with some different values
Same The same status and body
Not sent A write, left out unless you include writes

Rows are sorted worst first; Only differences hides the ones that are the same. Alike is the body similarity score the Authorization Matrix uses, from 0 to 100%.

Select a row for its detail: both sides’ status, time and host, and every field that differs — its JSONPath ($.items[0].price), whether it’s only on one side, a different type or a different value, and the value on each side. Compare shows the two responses side by side, line by line. Right-click a row to show the captured request in the traffic list.

  • Fields that change on every call: timestamps, request and trace IDs, nonces, CSRF tokens, ETags (the same list the Authorization Matrix ignores).
  • Key order and number formatting: 5 and 5.0 are the same.
  • Anything under Options › Ignore Fields…: one JSONPath per line, such as $.meta.build or $.items[*].stock; a path covers the fields inside it.

When two arrays have different lengths, that’s one difference on the array, and the items they share are compared one by one.

Open in Release Diff compares the two sides as two recordings in Release Diff: fields added, removed and retyped per endpoint, status changes and slower calls — useful when many requests share the same change.

Mirror mode copies live requests to a second host while you use the app, and compares each copy’s answer with the original’s. The app’s own requests aren’t changed or delayed: the copy is sent after the original finishes.

  1. Open Tools › Edit Map Remote Rules…, add a rule matching your API (such as https://api.example.com/*) and set where copies go: the Scheme, Host and Port of the second environment.

  2. Under When a request matches, choose Send a copy there and compare, and make sure Map Remote is on.

  3. Use the app. In Compare Environments, choose Mirror Live Traffic to watch the comparisons arrive.

Live requests copied to staging, each compared with the original's answer.

Mirror Live Traffic: the rule https://api.shopapp.example/* → https://api.staging.shopapp.example, 9 mirrored, 6 differ, 3 writes not mirrored, a Mirror writes too checkbox and an Edit in Map Remote button. The list compares Original and Mirror for each request, with GET /v2/users/me selected: Shape differs, 50% alike, $.loyalty only in Original and $.preferences only in Mirror.

The bar above the list shows the mirror rules and counts: requests mirrored, how many differ, writes not mirrored and copies skipped while busy. Mirror writes too also copies POST, PUT, PATCH and DELETE requests — they change data on the mirror host. At most four copies are in flight and five are sent per second; requests that arrive while the mirror is busy are counted and skipped, not queued, so a busy app never floods the mirror. The latest 500 comparisons are kept, and Clear empties them with the session.

A mirror rule doesn’t redirect anything, so a request the rule matches still goes to its own host and shows in the traffic list as usual.

The MCP server has two tools:

  • compare_environments sends captured requests (scoped with ids, app or host) to the right side — {host, scheme, port, headers, name} — and compares the answers with the captured responses, or with a left side given the same way. It returns the counts and each request that differs, with its fields. include_writes, every_request, ignore_fields, concurrency and requests_per_second work as in the window; format can be markdown or json.
  • mirror_report returns the mirror rules and the latest mirror comparisons. Turn a mirror on with upsert_rule (tool map_remote, "mirror": true). On Linux, the app and the headless engine mirror too, so mirror_report works over cloakctl and MCP there.
Ask your assistant
"Compare the ShopApp requests with api.staging.shopapp.example using Authorization: Bearer $STAGING_TOKEN,
and tell me which endpoints return a different shape."

cloakctl compare-envs compares a recording with another environment and exits with 2 when a difference matches --fail-on.

Terminal
cloakctl compare-envs --session prod-journey.har --right-host api.staging.example.com \
--right-header "Authorization: Bearer $STAGING_TOKEN" --fail-on shape --out env-diff.md
Option What it does
--session FILE / --har FILE The recording (required)
--right-host HOST The environment to compare with (required), with --right-scheme http|https, --right-port N and --right-header "Name: value" (repeatable)
--left-host HOST Send to this environment too, instead of comparing with the captured responses (with --left-scheme, --left-port, --left-header)
--host HOST / --app NAME Only this host’s or app’s requests
--include-writes / --every-request Also send writes; send every request, not one per endpoint
--ignore-field JSONPATH Leave a field out (repeatable)
--insecure Accept self-signed certificates
--format markdown|json / --out FILE The output, and where to write it
--fail-on RULE status (a status differs, or a side failed), shape (also fields and types), values or any (any difference), never (the default)
Exit code Meaning
0 No request matched --fail-on
1 A usage error, such as a missing --right-host or an unreadable file
2 At least one request matched --fail-on
  • Requests are replayed as captured. A request that depends on state on the server (an order that only exists in production) can differ for that reason alone; compare read-only endpoints, or use test data that exists in both.
  • Values are compared exactly. Data that legitimately differs between environments (prices, stock, user records) shows as Values differ; ignore those fields under Ignore Fields.
  • Only JSON bodies are compared field by field; other bodies are compared whole.
  • Redirects aren’t followed, so a redirect shows as its own status.
  • Mirror mode copies requests only while Cloak Proxy is running and Map Remote is on, and only exchanges that completed.