Python match on a Sume run status: terminal is not success

A Format run can be terminal as completed, failed, canceled or skipped. A structural match that never treats done as success, with a null-output case.

5 min readSume
All posts

Match on the whole run object, not on a boolean like is_done. A Sume Format run has six statuses: queued, processing, completed, failed, canceled and skipped, and four of them are terminal without being a success. Python's match statement lets you write each shape once, put the surprising one (completed with a null output) before the happy path, and fail loudly on a status you have never seen.

The six statuses

The runs docs list the statuses and the SDK warns that terminal is not success: the helpers resolve when a run stops moving and leave it to you to read status and error. skipped only appears when you sent on_active_run: "skip" and another run for the same Format was already active; it is a normal outcome, not an error, and it never produces a webhook. canceled is also silent on the webhook side.

The one that trips people is completed with no output. When a Format has an output schema and the agent could not satisfy it, the run can finish with output null and an output_error describing why, and the webhook envelope reads status: OK with outcome: degraded. A check for status == completed alone will happily pass that along. There is already a dedicated post on the degraded case; here the point is where it lives in your control flow.

A runnable match

The function below returns a string per branch so you can run it with no dependencies. It needs Python 3.10 or newer. Order matters: the null-output pattern is above the plain completed pattern, because match takes the first case that fits.

In production each branch would call into your own code: store the output, route the failure code, log the skip.

def handle(run: dict) -> str:
    match run:
        case {"status": "completed", "output": None}:
            return "completed but output missing: read output_error"
        case {"status": "completed"}:
            return "success: use output"
        case {"status": "failed", "error": {"code": code}}:
            return f"failed: route {code}"
        case {"status": "skipped"}:
            return "skipped: a run was already active"
        case {"status": "canceled"}:
            return "canceled: nothing to deliver"
        case {"status": "queued" | "processing"}:
            return "keep polling"
        case _:
            raise ValueError("unknown status")

for r in (
    {"status": "completed", "output": {"url": "x"}},
    {"status": "completed", "output": None, "output_error": {"code": "output_schema_unsatisfied"}},
    {"status": "failed", "error": {"code": "provider_credits_exhausted"}},
    {"status": "skipped"},
):
    print(handle(r))

Route failures by code

A failed run carries error.code, and you should branch on it, not on the message. provider_credits_exhausted is not your wallet and is not retryable; output_schema_unsatisfied, primary_output_missing and incomplete_assembly say the work ran but the deliverable did not materialise, so a continuation with previous_run_id is usually the right move; unattended_blocked means the agent needed a human. Retrying a failed run needs a new Idempotency-Key, because the old key replays the old run.

read 2026-10-03
StatusMeaningYour action
completed, output presentSuccessUse output
completed, output nullDegradedRead output_error; fix schema or continue
failedErrorRoute on error.code
skippedNo run startedWait for the active run, then re-trigger
canceledStopped by youNo webhook arrives
queued / processingNot terminalKeep polling

Keep the wildcard branch

The last case raises. A new status or an unexpected shape should be an alert in your logs, not a quiet pass-through into billing or publishing code. If you use the TypeScript SDK, the same discipline is an exhaustive switch with a never check, as in the job status post.

Wiring it to the SDK

In Python you will usually have the run as a decoded JSON object from a receipt or a webhook payload, which is exactly the shape the match patterns expect. Add a case for the webhook envelope if you route stored events through the same function: status: OK with outcome: degraded is the receipt's completed-with-null-output case in another spelling. Test the function with one fixture per status plus a deliberately unknown one, and keep the fixtures next to the code so a new status shows up as a failing test.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume