Skip to content

Debugging tools

Conditions and GraphQL operations

Since 0.4.0 Requires Cloak Proxy 0.4.0 or later.

Add conditions to any Cloak Proxy rule so it matches only requests with a certain header, query parameter, JSON body value or GraphQL operation, and see GraphQL operations and errors in the traffic list, filters, sidebar, Insights and Alerts.

Since 0.4.0

A rule’s URL pattern can’t tell apart requests that share a URL. GraphQL sends every operation to one endpoint, often /graphql, and many other APIs say what a request does in a header or a body field. Conditions fix that: every rule, in every tool, can also require a header, a query parameter, a value in the JSON body, some text in the body, or a GraphQL operation name or type. “Return a declined card only for the CreateOrder mutation from ShopApp 4.x” is one Map Local rule.

Cloak Proxy also understands GraphQL traffic on its own: the traffic list names each operation, a 200 response whose body carries an errors array is shown as a failure, the sidebar groups requests by operation, and Insights gives each operation its own endpoint row.

A Map Local rule for one GraphQL mutation, narrowed by a JSON body value and a header. The tester counts the captured requests it matches.

A Map Local rule named Card declined on checkout for POST https://api.shopapp.example/graphql, limited to ShopApp, with three conditions: GraphQL operation is CreateOrder, JSON body $.variables.input.payment is card, and Header X-App-Version matches regex ^4\. Below them: Matches 1 of 54 captured requests, with a Show Request button
  1. Open the rule in the Tools window (Tools › Tools…, or right-click a request and choose Tools).

  2. Under the URL pattern and Only these apps, click Add Condition and choose what to check: Request Header, Query Parameter, JSON Body Value (JSONPath), Body Text, GraphQL Operation Name or GraphQL Operation Type.

  3. Fill in the row: the header, parameter or JSONPath (when the condition needs one), the comparison, and the value.

  4. Read the line under the conditions. Matches N of M captured requests counts the requests already captured that pass the URL, method, app and every condition. Click Show Requests to select them in the traffic list. When none match, it names the condition that stopped the most of them, such as stopped by operation is CreateOrder.

A rule with conditions matches a request only when its URL, method and app match and every enabled condition passes. Conditions never widen a rule: they only narrow what the URL already matches. A rule with no conditions works exactly as before.

In the rule list, a rule with conditions shows the first one under its URL, for example operation is CreateOrder + 2 more.

Condition Name field What it checks
Header The header name, for example X-Feature Every value of that request header. Header names ignore case.
Query parameter The parameter name, for example page Every value of that parameter in the URL. A repeated parameter (?page=2&page=3) passes if any value does.
JSON body A JSONPath, for example $.variables.input.id Every value the path selects in the request’s JSON body. See JSON body values.
Body text None The request body as text, decoded from gzip, deflate or Brotli, up to 2 MB.
GraphQL operation None The name of the GraphQL operation the request runs, such as CreateOrder. A batch passes if any of its operations does.
GraphQL type None query, mutation or subscription.
Comparison Passes when
is A value equals the text.
is not No value equals the text. A header or field that isn’t there passes.
contains A value contains the text.
doesn’t contain No value contains the text.
matches regex A value matches the regular expression (not anchored; add ^ and $ to anchor it).
exists The header, parameter, JSON value or operation name is there, even if it’s empty.
is missing It isn’t there.

All comparisons ignore case, including regular expressions. A condition’s checkbox turns it off without deleting it; turned-off conditions are skipped.

When a condition can’t work as written, a warning shows under it: Enter a header name, That isn’t a valid JSONPath, That isn’t a valid regular expression, or The type is query, mutation or subscription. Until you fix it, an invalid regular expression matches nothing.

The JSON body condition reads the request body as JSON (after removing any Content-Encoding) and evaluates a JSONPath on it. $. at the start is optional: variables.id means $.variables.id.

JSONPath Selects
$.variables.input.total One field.
$.items[0].sku The first item’s sku.
$.items[*].sku Every item’s sku; is passes if any of them equals the value.
$..price Every price field at any depth.

Strings are compared without their quotes; numbers, true, false and null are compared as written (0, true). Objects and arrays are compared as compact JSON, so contains "sku":"A1" works on an array of items. A body that isn’t JSON, or is larger than 2 MB, has no values: exists fails and is missing passes.

Cloak Proxy finds the operation in every common way GraphQL is sent over HTTP:

Request Example
POST with a JSON body {"query": "mutation CreateOrder(…) {…}", "operationName": "CreateOrder", "variables": {…}}
A batch A JSON array of those objects; each entry is an operation.
GET with query parameters /graphql?query=query%20GetFeed…&operationName=GetFeed
application/graphql The document as the body.
Persisted queries {"operationName": "GetCart", "extensions": {"persistedQuery": {…}}}, with no document.

The name comes from operationName, or from the document. The type comes from the document itself, so a document with several operations, fragments or comments before the operation is read correctly. Persisted queries carry no document, so their type is a guess from the name (createOrder, updateProfile and similar are mutations). A body like {"query": "red shoes"} from a search box isn’t treated as GraphQL.

Right-click a GraphQL request and choose Tools and a tool. The new rule matches the request’s URL and has a GraphQL operation is … condition, and it’s named after the operation, for example mutation CreateOrder. The same happens for Break on Next Request, from the MCP server’s rule_from_request, and from Create Rule for This Operation on an operation in the sidebar.

Mock Sets record the operation with each GraphQL response, so a mock set answers GetUser with the GetUser recording and GetCart with the GetCart one, even though both went to /graphql. An operation that wasn’t recorded gets the set’s offline 404. Recordings made before 0.4.0 have no operation and still answer anything at their URL.

GraphQL servers usually answer 200 OK even when an operation fails, and put the failure in an errors array in the body. Cloak Proxy treats a GraphQL request whose 2xx response has a non-empty top-level errors array as a failure:

A 200 with GraphQL errors shows a red status and dot, and the response explains why.

The traffic list with GraphQL requests to api.shopapp.example, each with a pink operation badge. The selected mutation CreateOrder row shows a red 200 ✕ status. The response pane starts with a red banner: 1 GraphQL error, PAYMENT_DECLINED: Your card was declined.
Where What you see
Traffic list The status pill shows the code with a cross, such as 200 ✕, in red, and the status dot is red. Hover for the first error.
Inspector A red banner above the response: the number of errors, the first error’s code and message, and Details for every error with its path. (partial data) means some fields came back as well.
Filter bar A GraphQL Errors tab, with a count, appears once a response has had errors. It adds a GraphQL equals errors condition to Filters.
Sidebar The operation’s row shows how many of its requests had errors, such as 1 ✕.
Insights The endpoint’s Errors column counts them, and its details list GraphQL errors × N.
Alerts The new GraphQL errors (in a 2xx response) condition, and Any error (4xx, 5xx or failure) now includes them.

The banner above a response that carried GraphQL errors.

The response pane's Body tab with a red banner reading 1 GraphQL error, PAYMENT_DECLINED: Your card was declined, above a JSON tree with data.createOrder null and the errors array

A 4xx or 5xx response is already a failure and isn’t counted twice. Responses from REST APIs that happen to use an errors key aren’t flagged: only requests Cloak Proxy recognises as GraphQL are.

Once Cloak Proxy has seen a GraphQL request, the sidebar has a GraphQL group listing every operation by name, with a Q, M or S badge for its type, how many requests ran it, and how many came back with errors. Click one to list only its requests, whichever host or URL they went to. Right-click it to create a rule for the operation, pin it, or copy its name.

Each operation, its type, its request count and its errors.

The sidebar's GraphQL group with operations AddToCart, CreateOrder (selected, 1 ✕), GetCart, GetUser, GetViewer, SearchProducts and UpdateProfile (1 ✕), each with a Q or M badge and a count
  • GraphQL Operation column. Right-click the list’s header to show it. It shows the operation’s type and name (red when the response had errors), and sorts by name.
  • GraphQL filter field. In Filters and the quick filter’s Match in menu, GraphQL searches the operation (mutation CreateOrder, CreateOrder, mutation) and, for responses with errors, the word errors, each error’s code and its message.
  • Insights › Endpoints has one row per operation: POST api.shopapp.example /graphql with a mutation CreateOrder badge, its own call count, error rate and timings. Schema change detection compares each operation’s responses with that operation’s earlier responses.

Insights splits /graphql into one endpoint per operation.

Insights Endpoints: six rows for /graphql, one per operation (mutation CreateOrder with 1 error of 2 calls, query GetCart, query GetViewer, mutation AddToCart, mutation UpdateProfile with 1 error, query SearchProducts), then REST endpoints

Every tool with a matcher: Breakpoint, Map Local, Map Remote, Rewrite, Scripting, Block List, Allow List, Network Conditions and Alerts. Mock Sets match each recording’s GraphQL operation as described above. DNS Spoofing and No Caching have no matcher.

Conditions look at the request at the same point the rule’s URL does (see Which URL each step sees): Block List and Rewrite see the request as the app sent it, Map Local sees it after Rewrite, scripts and the request breakpoint, and so on. Response-stage rules match the request as the app sent it. Alerts match the recorded request.

The body is decoded and parsed at most once per request, and only when a rule whose URL already matched has a condition that needs it, so conditions cost nothing on requests they don’t apply to.

Use conditions from an AI assistant or the command line

Section titled “Use conditions from an AI assistant or the command line”

Every rule’s matcher takes a conditions list in exported rules, the control API and the MCP server’s upsert_rule:

A Map Local rule for one GraphQL mutation
{
"name": "Card declined on checkout",
"matcher": {
"url": "https://api.shopapp.example/graphql",
"method": "POST",
"conditions": [
{ "field": "graphQLOperation", "value": "CreateOrder" },
{ "field": "jsonPath", "name": "$.variables.input.payment", "operator": "equals", "value": "card" },
{ "field": "header", "name": "X-App-Version", "operator": "matches", "value": "^4\\." }
]
},
"statusCode": 200,
"body": "{\"data\":{\"createOrder\":null},\"errors\":[{\"message\":\"Your card was declined.\"}]}"
}
Key Values
field header, query, jsonPath, body, graphQLOperation, graphQLType
name The header, parameter or JSONPath. Not used by the other fields.
operator equals, notEquals, contains, notContains, matches, exists, missing. Defaults to equals, or exists when there’s no value.
value The text to compare.
isEnabled false turns the condition off. Defaults to true.

Sending conditions replaces the rule’s whole list. An unknown field or operator is refused with the allowed values.

To find GraphQL traffic:

  • MCP search_requests takes graphql_operation (a name, part of one, or a type) and graphql_errors: true, with or without text. Each result names its operation (graphql) and errors (graphqlErrors).
  • MCP list_requests takes graphql_operation, and errors_only includes GraphQL errors. Its lines show the operation in braces: #52 200 POST [ShopApp] https://api.shopapp.example/graphql {mutation CreateOrder} 410ms GraphQL errors: PAYMENT_DECLINED: Your card was declined.
  • cloakctl list --graphql CreateOrder does the same from the command line, and cloakctl list --errors includes GraphQL errors.
  • Conditions read the request only. To act on the response, use Rewrite response actions, a script or an Alert trigger.
  • Bodies over 2 MB aren’t parsed for JSON values, GraphQL operations or GraphQL errors. Body text reads the first 2 MB.
  • Test a URL checks the URL and method only. When a rule has conditions and the URL matches, it says URL matches; the captured-request count above it checks the conditions.
  • GraphQL subscriptions over WebSocket aren’t read; only GraphQL sent as HTTP requests is.