Inspect traffic
Streaming responses
Since 0.4.0 Requires Cloak Proxy 0.4.0 or later.
Watch Server-Sent Events, JSON-lines streams, multipart GraphQL responses and long chunked bodies event by event in Cloak Proxy, live, with when each event arrived. Streams pass through unbuffered.
Some responses arrive a piece at a time: an AI assistant’s reply token by token, live scores, order updates, build logs, a GraphQL subscription. Cloak Proxy passes these streams on to the app as they arrive, and the inspector shows them event by event in a Messages tab, updating while the stream is open. Each event shows when it arrived and how long it took after the one before, so a slow first token or a stalled stream is easy to spot.
An assistant's reply streamed as SSE. Event 9 arrived after a 1.42 s stall.
- Live, Ended, Closed by app or Broke
- Show one event type
- Event count, time to the first event, longest gap
- When each event arrived; click a tick to select it
- Wait since the previous event, orange from 1 s
- The selected event's fields and data
What counts as a stream
Section titled “What counts as a stream”| Response | Shown as | Example |
|---|---|---|
text/event-stream |
Messages: one row per Server-Sent Event | Most AI APIs, live feeds, notifications |
application/x-ndjson, application/jsonl, application/json-seq and similar |
Messages: one row per JSON line | Local model servers, log and export APIs |
multipart/mixed |
Messages: one row per part | GraphQL @defer and @stream (incremental delivery) |
Any other response with no Content-Length that is still arriving after half a second |
Chunks: one row per network read | Long polling, progress output, chunked feeds |
The traffic list marks these requests with a teal badge before the URL: SSE, JSON lines, Multipart or Streamed, and Live · in front while the stream is still open.
Streams pass through as they arrive
Section titled “Streams pass through as they arrive”Cloak Proxy forwards a stream’s bytes to the app as soon as the server sends them, instead of waiting for the whole response. Event sources, chat UIs and progress bars keep working through the proxy, and closing the stream in the app closes it at the server too.
Watch a stream
Section titled “Watch a stream”- Make sure the connection is decrypted. For HTTPS, turn on SSL Proxying for the host or the app.
- Select the request in the traffic list. A stream that’s still open shows Live · in its badge.
- The response pane opens on the Messages tab, with the event count. Events are added as they arrive, and the list scrolls to the newest one.
- Click an event to see its data below the table. Selecting an event stops the list following new ones. Click the follow button at the right of the toolbar to follow again.
Streams remember their own tab: if you switch a stream to Body or Raw, the next stream you select opens on that tab too, while ordinary responses keep theirs.
A stream that's still open. The two long waits are orange.
Messages table
Section titled “Messages table”| Column | Shows |
|---|---|
| # | The event’s position in the stream |
| Time | When the event finished arriving, counted from the start of the request |
| Gap | How long after the previous event it arrived. Orange from 1 second, red from 10 seconds. |
| Type | The SSE event: field (message when there is none), or a multipart part’s Content-Type. Blocks that only set the reconnect delay show as retry. Not shown for JSON lines and chunks. |
| ID | The SSE id: field, when any event has one |
| Size | The size of the event’s data |
| Data | The data on one line. Line breaks inside it show as ⏎. |
Two small badges can follow the data:
| Badge | Meaning |
|---|---|
| Cut off | The stream ended in the middle of this event: it had no blank line (SSE), no newline and wasn’t valid JSON (JSON lines), or no closing boundary (multipart). |
| No data | An SSE block with fields but no data: line. Browsers’ EventSource doesn’t deliver these, so the app never sees them. |
Toolbar
Section titled “Toolbar”| Control | What it does |
|---|---|
| State badge | Live while the response is streaming. Ended when the server finished it, Closed by app when the app hung up first, Broke when the connection to the server dropped (hover to see why), Buffered when a tool held it. |
| Filter events | Shows events whose data, type or id contains your text, ignoring case |
| All types | Shows one event type. Each type lists how many events it has. Only there when the stream has more than one type. |
| Summary | How many events, how long after the request the first one arrived, and the longest gap. times estimated appears for compressed streams (see below). |
| Follow button | While the stream is live: keeps the newest event in view |
Arrival strip
Section titled “Arrival strip”Under the toolbar, a strip runs from the start of the request (0) to the last event. The gray bar at its start is the wait for the response headers, and each tick is an event, so bursts and stalls show at a glance. Ticks of events hidden by a filter are fainter. Click anywhere on the strip to select the nearest event.
Read an event
Section titled “Read an event”The lower half shows the selected event’s number, type, id, retry value, when it arrived, the gap, and its size. Its data opens in the body viewer: JSON as a tree you can filter and query with JSONPath or jq, other text as Raw, binary chunks in Hex. Click the copy button to copy the data.
Right-click an event for Copy Data, Copy Data as Pretty JSON, Copy Event as Sent (with its event:, id: and data: lines), or Show Only type Events.
JSON lines from a local model server, one event per line.
GraphQL subscriptions
Section titled “GraphQL subscriptions”When a stream carries GraphQL, the response pane adds a GraphQL tab that groups the traffic by operation. It recognizes:
| Transport | How it’s recognized |
|---|---|
graphql-transport-ws (the graphql-ws library) |
A WebSocket with that subprotocol, or whose messages use subscribe and next |
graphql-ws (the older subscriptions-transport-ws) |
A WebSocket with that subprotocol, or whose messages use start and data |
| GraphQL over SSE | A GraphQL request answered with text/event-stream (next and complete events) |
| Incremental delivery | A GraphQL request answered with multipart/mixed (@defer, @stream) |
Two subscriptions on one WebSocket, and the results of the selected one.
- Whether the server acknowledged the connection
- Active, Completed, Failed or Stopped
- Results, Query, Variables and Errors
| Column | Shows |
|---|---|
| Operation | The kind (subscription, query, mutation) and name, or Anonymous |
| ID | The id the protocol gave the operation |
| Status | Active while results may follow, Completed when the server finished it, Failed when it sent an error, Stopped when the app stopped it or the socket closed |
| Results | How many results arrived |
| First result | When the first result arrived, from the start of the connection |
Select an operation to see its Results (each with its time and gap, and its payload as JSON), its Query, its Variables and any Errors. Results that carry errors have a red errors badge. The tab’s header names the transport and, for WebSockets, whether the server answered connection_init with connection_ack.
The Messages tab still lists every frame or event, including protocol messages such as ping, pong and connection_init.
Timing details
Section titled “Timing details”| Detail | How it’s measured |
|---|---|
| Event time | When the network read containing the event’s last byte reached Cloak Proxy, counted from when the request started |
| Response headers | When the response head arrived (the gray bar on the arrival strip, and headersMs in MCP) |
| Gap | The difference between an event’s time and the previous event’s |
| Compressed streams | Cloak Proxy decompresses gzip, deflate and Brotli streams to read the events, then places each one in time by its position in the stream. The summary says times estimated. |
| Very long streams | Up to 20,000 network reads keep their own times. Beyond that, neighbouring reads are merged, so some events take the time of the read after them, never an earlier one. |
| Body size | Events past the Max captured body size aren’t recorded, but the stream keeps flowing to the app. |
Arrival times are saved in Cloak Proxy session files. HAR files have no place for them, so a stream opened from a HAR shows its events without times.
With an AI assistant
Section titled “With an AI assistant”The get_stream_events MCP tool returns a streamed response event by event, with each event’s type, id, data (parsed when it’s JSON), time and gap, plus the time to the first event, the average and longest gap, and how the stream ended. For GraphQL streams it adds each operation with its status, result count and last result. Pass after with the lastIndex of the previous call to get only newer events while a stream is still open. See MCP tools reference.
Use Cloak Proxy to look at request #42’s stream. How long until the first token, and where did it stall?
From the command line
Section titled “From the command line”cloakctl events prints a stream one event per line: index, time, gap, type, id and data.
cloakctl events 42 # from the running appcloakctl events 42 --follow # keep printing until the stream endscloakctl events 42 --type message_deltacloakctl events 42 --session run.cloaksession# https://api.assistant.example/v1/messages · SSE · 16 events1 381 ms message_start {"type":"message_start",…}2 382 ms +1 ms ping {"type":"ping"}9 2.121 s +1.418 s content_block_delta {"type":"content_block_delta",…}| Option | What it does |
|---|---|
--follow |
Keeps printing new events every half second until the stream ends |
--type TYPE |
Only events of this type |
--json |
Prints the get_stream_events result instead |
--session FILE / --har FILE |
Reads the request from a saved session instead of the running app |
--port N |
The app’s port, when it isn’t the configured one |
It exits 0 when the stream ended cleanly or is still live, 2 when the server broke the stream or its last event was cut off, and 1 when the request isn’t a stream. In CI, record with cloakctl run --record run.cloaksession, then check a streaming endpoint with cloakctl events ID --session run.cloaksession.
Limits
Section titled “Limits”- Streams over WebSocket are shown in the WebSocket Messages tab; only GraphQL ones also get the GraphQL tab.
- Editing a stream’s events in flight (dropping, delaying or injecting events) isn’t available yet. A Breakpoint on the response holds the whole stream and lets you edit it as one body.
- gRPC streaming calls are shown in the body viewer, not as events.