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.

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.
| Run status | Webhook sent | How you learn about it |
|---|---|---|
| completed | Yes, status OK | POST, or poll status_url |
| failed | Yes, status ERROR | POST, or poll status_url |
| canceled | No | Poll status_url until canceled |
| skipped | No | Read 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
- Five gates between a Sume MCP write session and your wallet
A hosted Sume MCP write session has five gates before money moves: scope, idempotency_key, dry_run, an optional max_spend_usd, and wallet admission.
- A 5,000-character voiceover costs $0.2375: sizing the run cap
Sume text-to-speech is $0.0475 per 1,000 characters: $0.2375 for 5,000, $0.95 for 20,000. Use the table to set generation_spend_cap_usd on an Agent Completion.
- Hourly Sume schedule worst case: 24 runs a day at the $1 default cap
If a Sume schedule has no spend cap set, each run is capped at $1.00 of generation. Hourly cadence means up to $24 a day and $720 over 30 days. Show the math.
- MiniMax H3 768p clips inside a $2 Agent Completion cap
At $0.075 per second, a $2 Agent Completion cap fits 25 s of MiniMax H3 at 768p: one 15 s and two 5 s clips for $1.875. Table of the combinations.
Written by Sume