Format run on_active_run: allow, skip or reject with a 409?

on_active_run sets what a second Format run does while one is in flight: allow runs both, skip records a skipped run, reject answers 409 format_run_in_progress.

5 min readSume
All posts

on_active_run on a Format run request chooses what happens when a run of the same Format is already in flight: allow (the default) runs both at once, skip records a run with status skipped, and reject answers 409 format_run_in_progress. Pick skip for cron-style triggers where a missed tick is fine, and reject when your caller should know it collided.

The rules below come from Calling a Format and Runs and results.

What do the three values do?

The default is allow: the run starts concurrently, and only your workspace generation concurrency limit can slow it down. The other two are opt-in.

Scheduled Actions default to skip, so do not copy a Scheduled body into a Format call and assume it behaves the same. A direct Format run behaves like allow unless you say otherwise.

on_active_run values, read 2026-10-02
ValueWhat happensWhat you get back
allow (default)Runs concurrently; workspace concurrency still appliesA normal queued run
skipRecords a run that never executesA terminal run with status skipped, skip_reason set, next_action retry_later
rejectRefuses the request409 format_run_in_progress

Is skipped an error or a result?

A result. The run lifecycle is queued -> processing -> completed | failed | canceled | skipped, and skipped means it never ran because you sent skip and another run was in flight. next_action on that receipt is retry_later, and skip_reason says why.

Two details matter for integrations. A skipped run is already terminal, so cancel has nothing to stop. And a webhook is delivered once per run on completed or failed only, never on canceled or skipped, so a receiver that waits for a push on every run will wait forever for a skipped one. Branch on the create response instead.

How do I handle it with the SDK?

subscribeFormatRun and waitForRun resolve for any terminal status, so a skipped run comes back as a value, not an exception. Put skipped in your switch next to failed:

import { createSumeClient, subscribeFormatRun } from "@sume-com/sdk";

const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });

const run = await subscribeFormatRun({
  client,
  path: { handle: "acme", slug: "product-promo" },
  idempotencyKey: "nightly-promo-2026-10-02",
  body: {
    input: { product_url: "https://shop.example.com/p/8823" },
    on_active_run: "skip",
  },
});

switch (run.status) {
  case "completed": console.log(run.primary_output_url); break;
  case "skipped":   console.log("busy, try later:", run.skip_reason); break;
  default:          console.error(run.status, run.error);
}

When should I choose reject instead?

Use reject when the caller is a person or a pipeline step that must not silently lose work. A 409 format_run_in_progress is an error at create, so nothing runs and nothing is charged, and your code decides whether to wait, queue or tell the user.

Use skip when the trigger repeats on its own and a later tick will cover the gap. Use allow when each run is a distinct unit of work, such as one render per order, and send an Idempotency-Key derived from the order so a retry cannot double-run it.

Do not confuse this with the idempotency conflicts. 409 idempotency_key_in_use is two requests with the same key at the same moment, and it is retryable after about a second. format_run_in_progress is two different requests against one Format, and only reject produces it.

For many items at once, a bulk queue is the better tool than firing creates with allow and hoping the concurrency limit sorts it out.

How should I test each branch?

Cheap tests beat guesses here. Start one long Format run, then send a second create for the same Format with each value of on_active_run and log what comes back. With allow you should see a second queued or processing run and a new run id. With skip you should see a terminal receipt whose status is skipped, with skip_reason populated and next_action set to retry_later. With reject you should see an HTTP 409 and the code format_run_in_progress, and no run id at all.

Check the spend side as well. A skipped run never executed, so there is nothing to bill, and a reject fails at create, where the errors page says nothing runs and nothing is charged. Only allow can add spend, and the workspace generation concurrency limit still applies to it, so a burst of allow creates can sit queued rather than run in parallel.

Keep your own bookkeeping honest too. If your system counts a trigger as handled once the create call returns, a skipped receipt means that tick produced nothing. Record the skip, and decide whether a later trigger or a retry with the same idempotency key from your side should cover it. Remember that a retry after a skip is a new decision: use a new key if you want a new attempt, because the same key with the same body replays the original receipt.

Keep this reference next to your trigger code.

  • Default is allow; scheduled Actions default to skip.
  • skipped is terminal and carries next_action: retry_later.
  • No webhook is delivered for skipped or canceled runs.
  • reject is 409 format_run_in_progress, raised before any run exists.
  • Always send an Idempotency-Key derived from what you are making.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume