Daily Sume schedule: skipped and canceled runs send no webhook

A Sume run webhook fires only on completed or failed. For a daily schedule, a skipped or canceled run is silent, so add a list-runs check each morning.

5 min readSume
All posts

If you only listen for webhooks on a daily Sume schedule, you will not hear about a skipped or canceled run. Sume sends one signed POST when a run completes or fails. A skipped run never starts work and a canceled run has its own API path, and the docs say neither delivers a webhook. The fix is a small check that lists recent runs after the time you expect the run to have finished.

Which outcomes produce a POST

The run webhook page states the rule per outcome. The table below is read from the Sume docs on 2026-10-09 and covers Action runs, which is what a schedule creates.

The same holds for a Format run, except that its overlap default is allow rather than skip, so a Format run does not skip on its own. The schedule default is the one that surprises people who move from Formats.

Webhook delivery per run outcome, Sume docs read 2026-10-09
Run statusWebhook sentHow you learn about it
completedYes, status OKPOST, or poll status_url
failedYes, status ERRORPOST, or poll status_url
canceledNoPoll status_url until canceled
skippedNoRead status on the create response, or list runs

Why a daily schedule can skip

Only one run of a schedule is active at a time. The default for on_active_run on Action runs is skip. If yesterday's run is still processing when today's cron fires, the new run is recorded with status skipped and skip_reason previous_run_active, and the receipt tells a poller to retry later through next_action.

That is a valid result, not an error, so the HTTP status is 200 and no alert fires. A schedule whose runs routinely take longer than their cadence will skip quietly for days if nothing watches for it.

A morning check against the list endpoint

GET /v1/actions/{action_id}/runs returns recent runs newest first, with limit from 1 to 100. The script below reads the latest five and decides if a run in the last 26 hours completed. It needs a key with actions:read and prints one line you can route to your pager. It does not read the schedule's instructions, which the public shape omits.

import json, os, urllib.request
from datetime import datetime, timedelta, timezone

KEY = os.environ["SUME_API_KEY"]
ACTION = os.environ["ACTION_ID"]
URL = f"https://api.sume.com/v1/actions/{ACTION}/runs?limit=5"

def latest_runs():
    req = urllib.request.Request(URL, headers={"Authorization": f"Bearer {KEY}"})
    with urllib.request.urlopen(req, timeout=20) as r:
        return json.load(r)["data"]

def check(max_age_hours=26):
    cutoff = datetime.now(timezone.utc) - timedelta(hours=max_age_hours)
    for run in latest_runs():
        made = datetime.fromisoformat(run["created_at"].replace("Z", "+00:00"))
        if made < cutoff:
            break
        if run["status"] == "completed":
            return "ok"
        if run["status"] in ("skipped", "canceled", "failed"):
            return f"alert: {run['status']} {run.get('skip_reason')}"
    return "alert: no run in window"

print(check())

Choosing the window

Pick the window from the schedule's cadence plus its slowest normal run, not from the cron expression alone. The 26 hours above leaves two hours of slack on a 24-hour cadence. A skipped run inside the window is reported as an alert rather than ignored, so a skip still wakes someone even though no webhook told you.

Keep the webhook as well. It is the fast path for completed and failed runs, and dedupe on the envelope request_id, which equals run_id and stays stable across retries. The check above only covers the cases a POST cannot.

The script is read-only and safe to run from a cron job of your own. Send its output to the same channel as your webhook alerts, so a quiet morning means every check returned ok. If you need history for a post-mortem, page the list with next_cursor until has_more is false, since the cursor is opaque and must be sent back unchanged.

Sources

Related posts

More in Agents

All Agents posts

Written by Sume