Follow a run as it happens
Open an event stream on a run, and see each step start and finish as it happens.
1.Start a run
Start a run and keep its ID. Any run works, however it started: Run a workflow, a webhook or a schedule.
Open the stream straight after. It has no replay: an event from before you connected is gone.
RUN=$(curl -s -X POST "$GORUNNER/api/workflows/$ID/run?environment=production" \
-H "Authorization: Bearer $GORUNNER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Ada Lovelace"}' | jq -r .data.id)2.Open the stream
Ask for /api/runs/{id}/stream with Accept: text/event-stream. The token needs run:read.
The first event is connected. Each one after is a JSON object on a data: line. Here the first step had already finished, so the stream starts at the wait.
You should see run_completed, then the stream ends by itself.
curl -N "$GORUNNER/api/runs/$RUN/stream" \
-H "Authorization: Bearer $GORUNNER_TOKEN" \
-H "Accept: text/event-stream"event: connected
data: {"runId":"a274b3d9-…"}
data: {"type":"run_paused","runId":"a274b3d9-…","nodeId":"wait","status":"paused","data":{"resumeAt":"2026-10-25T18:22:09Z","resumeMode":"time","waitStep":"wait"}}
data: {"type":"run_resumed","runId":"a274b3d9-…","nodeId":"wait","status":"running"}
data: {"type":"step_completed","runId":"a274b3d9-…","nodeId":"wait","status":"completed"}
data: {"type":"step_started","runId":"a274b3d9-…","nodeId":"lower","status":"running","data":{"attempt":0}}
data: {"type":"step_completed","runId":"a274b3d9-…","nodeId":"lower","status":"completed","data":{"attempt":0,"duration_ms":0}}
data: {"type":"run_completed","runId":"a274b3d9-…","status":"completed"}3.Read the events
run_started- The run started.
step_started- A step started.
data.attemptcounts from 0. step_completed- A step finished, in
data.duration_ms. A Branch adds which arms it took:data.matchedBranchesanddata.matchedBranch. step_retrying- A step failed and will try again:
attempt,maxRetries,delay_ms, after astep_failedfor that try. step_failed,step_skipped- A try of a step failed, with
error, or the step was skipped. run_paused,run_resumed- The run waits on a Delay or a person, then goes on.
run_completed,run_failed,run_cancelled- The end. Nothing follows.
nodeId is the step’s name. A step’s output isn’t in the stream: read it with Get a run.
const res = await fetch(`${GORUNNER}/api/runs/${RUN}/stream`, {
headers: { Authorization: `Bearer ${TOKEN}`, Accept: "text/event-stream" },
});
if (res.status === 204) process.exit(0); // already over: read the run
const decoder = new TextDecoder();
let buffer = "";
for await (const chunk of res.body) {
buffer += decoder.decode(chunk, { stream: true });
let end;
while ((end = buffer.indexOf("\n\n")) >= 0) {
const frame = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
const data = frame.split("\n").find((l) => l.startsWith("data: "));
if (!data || /^event:/m.test(frame)) continue; // connected, reconnect
const ev = JSON.parse(data.slice(6));
console.log(ev.type, ev.nodeId ?? "", ev.status);
}
}import json, requests
with requests.get(
f"{GORUNNER}/api/runs/{RUN}/stream",
headers={"Authorization": f"Bearer {TOKEN}",
"Accept": "text/event-stream"},
stream=True,
) as res:
event = None
for line in res.iter_lines(decode_unicode=True):
if line.startswith("event: "):
event = line[7:] # connected, reconnect
elif line.startswith("data: ") and event is None:
ev = json.loads(line[6:])
print(ev["type"], ev.get("nodeId", ""), ev["status"])
elif line == "":
event = None # a blank line ends a frame4.When it ends, or breaks
- The run ends
- The server closes the stream after the last event.
- Already over
- Opening it answers
204 No Content. Read the run instead. event: reconnect- The server is going away, or you read too slowly. Connect again, then read the run for what you missed.
: ping- Every 25 seconds, to keep the line open. Ignore it.
- Too many
- One token may hold 50 streams on one server. The next is
429 TOO_MANY_STREAMS.
A run_paused event never carries a resume link. Read a waiting step’s resumeUrl from its output with Get a run.
Related
- Stream a runEvery response
- Get a runIts state and each step’s output
- RunsTest and production runs, and the six states · 4 min
Checked against 12d85b3b