Format run stuck on processing: stalled or slow? Read events_url
A Format run can show processing for a long time. The last at value on events_url is the progress clock: if it stops moving for minutes, the run is stalled.

Read GET /v1/format-runs/{run_id}/events and look at at on the last entry. The docs call it the progress clock of the run. While it keeps moving, the run is slow. If it does not move for several minutes, the run is stalled, and Sume finalizes it at the bounds it publishes. status: "processing" alone cannot tell you which of the two you have.
This matters for long-form video, where the docs describe 15 to 30 minutes of work. A run that sits on processing for twenty minutes is normal. A run whose clock froze is a different problem, and you want to see it before the deadline.
What the timeline returns
The events route returns a data array of phase entries. Each entry has at, phase, status and duration_ms. There are three phases, and they follow the life of a run.
| Phase | What it covers |
|---|---|
preparing | All the work before the agent gets the run |
running | The agent doing the recipe, where most of the time goes |
finalizing | Teardown and output harvest |
Each entry also has a status of pending, running, done, warning, error or skipped. duration_ms is filled when the phase measured itself, and it is null for a phase that is still running. Consecutive entries with the same phase and status are merged into one, and the at of the merged entry keeps increasing. That merge is why the last entry's at works as a clock.
Measure the silence
The sample reads events_url from the receipt, so you never build the path yourself. It returns the minutes since the last at. A failed timeline read returns null and tells you nothing about the run, because a 429 or 5xx on a read is a read failure only. Pass it SUME_API_KEY in the environment. With a run id as the first argument it reads that run's timeline; with no argument it shows the action and agent case.
const key = process.env.SUME_API_KEY;
if (!key) throw new Error("set SUME_API_KEY");
// Minutes since the progress clock (`at` on the last timeline entry) moved.
// Only Format runs have an events route; action and agent receipts say events_url: null.
export async function silentMinutes(run) {
if (!run.events_url) return null;
const res = await fetch(run.events_url, { headers: { "x-api-key": key } });
if (!res.ok) return null; // a failed timeline read says nothing about the run
const { data } = await res.json();
const last = data.at(-1);
return last ? (Date.now() - Date.parse(last.at)) / 60_000 : null;
}
const base = process.env.SUME_API_BASE_URL ?? "https://api.sume.com/v1";
const run = process.argv[2]
? { events_url: `${base}/format-runs/${process.argv[2]}/events` }
: { events_url: null };
console.log(await silentMinutes(run));Action and agent runs have no timeline
Only Format runs have an events route. Receipts of action runs and agent completions report events_url: null, and the SDK helpers ignore timeline: true for those families. For them, status_url and the terminal webhook are all you have. The same holds for the log-stream idea: the timeline is a phase timeline, not a log feed. Sume does not publish agent output, tool calls or sandbox internals there, and the docs say it will not in the future. There is also no push channel for progress.
What to do with the number
- Silent for a few minutes while
expires_atis still in the future: log it with the run id and keep polling. Do not cancel yet; a cancel does not refund generation that already finished. - Silent and
expires_atclose: stop polling atexpires_at. After that deadline Sume force-finalizes the run asfailed, and the receipt carries the error. - Silent on a run that is still
queued: readqueue.stateinstead.runtime_unavailablemeans nothing claimed the run, andretry_after_secondssays how long to back off. - Reading the timeline on every poll doubles your read requests for the same run. Reads have their own budget, 40 times the write budget, so creates are safe, but poll the timeline at a slower rhythm than status.
Where it fits
Use status for the decision "is it done", and the events clock for the decision "is it moving". The Runs and results page has the lifecycle, the expires_at rule and the cancel semantics. If you only care about the final answer, skip the timeline and wait for the single terminal webhook.
Sources
Related posts
More in Developers
- GET result_url returns 409 run_not_completed: poll status_url first
A 409 run_not_completed from result_url means the Format run is not terminal. Read details.status, poll status_url with backoff, then fetch the result.
- 40 Omni clips, 3 fail, 2 canceled while queued: where the wallet ends
Forty 8-second Omni clips at 720p reserve $40.00. With 3 failures and 2 cancels before start, the wallet captures $35.00.
- generation_spend_cap_usd on a Format run: null is $500, 0 is a 400
On a Format run request, omit generation_spend_cap_usd for the Format cap, send a number up to 500, null for the $500 maximum. 0 or above 500 returns 400.
- Get transcript text from a captioned video: caption jobs return none
A Sume caption job returns the burned video, not the transcript. For text and word times run STT or video inspect with transcribe. Prices and a recipe.
Written by Sume